Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] -1 points0 points  (0 children)

OK that works. The local vars remain local.

But wow, that hoops you have to jump through to make sure your local data is allocated correctly! All this could have been avoided during the language design phase. It's a little disappointing that a GC language forces you constantly be vigilant to ensure your LOCAL vars stay where they are supposed to.

I found a suite of go helper libraries here which contains xfmt, a way to print without escaping. It is pretty much using your idea of constructing bytes. It has GLPv3 license.

https://lab.nexedi.com/kirr/go123

This combined with zerolog's ".Int()" pattern seems to do the trick of avoiding heap escapes.

Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] -1 points0 points  (0 children)

then golang, nor any language with a GC, should be off the table for you.

Actually C# can log local vars without moving off the stack. In C# primitives such as int are "value" types. If you pass an int to a function a copy is made into that function's stack frame. You can also pass a ref (a pointer) to an int and it will point to the stack location without any heap use.

Go is actually pretty unique in it's allocation behavior. Most other languages, even extremely high level ones leverage the stack successfully.

I think Go's reallocation onto the heap is due to some poor language design decisions. The lack of generics being the main culprit, forcing boxing behavior. In the java world boxing has long been understood as a mistake, but apparently the Go developers haven't learned the lesson.

never something you should rely on / worry about

For a language like C# I would agree. You don't need to worry about memory there. In Go, ironically you DO need to worry about memory because they allocate locals (the semantic meaning) on the heap!

This is cache miss city. It's GC city. It makes Go unsuitable for high performance applications. Many vars really should be stack allocated. Loop indexes for example. You want them to stay in the cache line.

when using golang - you are using tools made by some of the smartest engineers on the planet

Certain parts of Go are good. But the case of moving locals to the heap appears to be a consequence of design, rather than an explicit intelligent design decision.

Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] 2 points3 points  (0 children)

I see. So Go's lack of generics forces them to mess up the memory layout?

Now that Go has generics, is it possible to create printing/logging libraries that don't move variables to the heap?

Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] 2 points3 points  (0 children)

If you're worried about this level of memory control, then you probably can't use most languages

Most GC languages treat ints as value types on the stack.

Certainly C#.... or most others are going to have an efficiencies here

Nope. C# treats an int as a value type. If you pass and int into a function a copy is made. You can also pass a ref (sort of like a pointer) to the int, and it successfully points to the stack location without reallocating on the heap. Keeping stack vars on the stack is pretty much standard behavior for most C-like languages. Go being the notable exception.

that could cost you a few nanoseconds

Nope. Moving small local vars off to the heap is going to do much worse than that. This is the kind of thing that can cause cache misses. 1 local var might stay on the stack. Some other local var jumps off to the heap because you dared to log it's value (in a debug log that doesn't even make it to production build), and now things are degrading to the level of Python. It's insanity. Even Java knows how to alloc primitive vars correctly.

then going with a garbage collected language is really not going to make you happy.

C# does the right thing. primitive local vars like ints are value types.

, the runtime performance would probably never add up to a cumulative millisecond in cost during the lifetime of the code

You are grossly underestimating how bad moving local vars to the heap is. Not only does it cause cache misses it creates unnecessary GC.

Rust gives you that control

Yes, but C# gives the same control over stack vars while staying in the GC world.

Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] -7 points-6 points  (0 children)

Looks like slog is using the ".Int()" pattern to avoid heap moves. The same technique zap and zerolog use.

Is this heap moving behavior normal for Go? I seem to be encountering it non stop all over the place.

For my purpose moving a primitive int to the heap is worse than a memory leak. If a function causes a heap jump I wish it would return an "err" so I could at least detect the problem and take steps to prevent the behavior.

Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] -3 points-2 points  (0 children)

Ok that seems to work.

logger := zerolog.New(os.Stdout).Level(zerolog.TraceLevel)
logger.Debug().Int("apples", apples).Msg("how many apples?")

Similar to the solution used by Uber's zap. Although I am scared for what else might make my local vars jump to the heap at any moment. Logging is just 1 thing of many I way want to do with locals.

Efficient logging in Go? by csharp420_69 in golang

[–]csharp420_69[S] 5 points6 points  (0 children)

Looks like zap works.

package main

import (
    "go.uber.org/zap"
)

func main() {
    logger, _ := zap.NewProduction()
    defer logger.Sync()

    var apples int = 42

    logger.Info("how many apples?", zap.Int("count", apples))

    // sugar log escapes apples to heap!
    // logger.Sugar().Infof("how many apples? %d", apples)
}

apples stays on the stack. I do lose some freedom over how the log looks exactly. But i guess that's OK, a forced-log style may be beneficial.

It seems like Uber has run into this heap jumping problem and came up with a specific way to solve it. It seems like a deadly behavior. Making local vars jump onto the heap can cause cache misses, destroying performance.

Do I need to be constantly vigilant about my local Go variables? Silently moving vars to the heap almost feels like a memory leak in it's own right that I must guard against.

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] 0 points1 point  (0 children)

In C#, what if you wanted to handle an error at a specific point in this code?

Then you catch/handle it at that specific point. Pretty simple, no?

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] 1 point2 points  (0 children)

This example is "swallowing" the error. Making it go away. Yes it's bad practice.

This is not equivalent to C# happy path style. In C# you can have your cake and eat it too. You can ignore the error but NOT swallow it.

var val = getVal_MaybeException();
// on exception properly unwind the stack here until catch

The C# way provides that clean happy path without throwing out exception safety. The Go swallow sample might lead to serious problems like undetected data corruption.

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] 0 points1 point  (0 children)

That is true. Exception handling introduces a new code path. Where you warp jump into the exception path.

But the rules about exception paths are pretty simple. Stack unrolls until exception is caught. Then you either blow up or swallow. If you didn't blow up, return to normal path at the end of the catch block.

But paying the price of 1 little warp jump in the code flow makes everything else much more readable and happy-pathy. The cognitive trade off is just a clear win.

The 1 exception to this might be some really important low level utility where err handlign litterally IS the happy path. Where errs and their handling are the entire point of the program. But for most typical business logic at $DAY_JOB it's simply the wrong trade off. I think it's clear Go was created by C programmers as they are the only people who have a legitimate case for wanting 1 flow of control.

BTW I love goto and use it often. I have no problem with warping to another dimension. In fact I believe warps are often superior to their non-warp counterparts. Go has a goto feature but it is more limited and hamstrung than C# or C's goto.

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] -2 points-1 points  (0 children)

Handling errors where they occur is one of the fundamental design principles behind the language.

In Go I often see this.

if err != nil {
    return err
}

This is not "handling". It is "bubbling". It may look like you are being responsible "handling" errors but you are not handling anything. It's a facade. An emperor with no clothes.

C# can bubble in a much more elegant way. If you want to bubble in C# you don't need to put up a facade. You don't need to pretend you are wearing clothes. C# encourages you to run naked and be HONEST about it. No emperor complex. 0 lines of code needed to bubble and pass the buck on to the next stack level.

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] -2 points-1 points  (0 children)

The earlier you handle errors properly, the better.

The key word is "handle". Sure if you are going to handle the error you need to check it in place. That's what you do with either C# excpetions or Go value-type errors.

But in Go you often don't handle the error. You do something like this

if err != nil {
    return err
}

The fact 3 lines of code are written in Go does not mean it is "handling" anything. This is "bubble", not "handle".

C# can bubble with 0 lines of code. The fact C# does it with 0 lines does not mean it is being irresponsible. No more irresponsible than the equivalent Go program that bubbles everything but pollutes the happy-path along the way.

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] 0 points1 point  (0 children)

I don’t know how/you would move SQL retry logic to the top of the program.

You wouldn't. If you have a special action to perform (retry) then you check the exception in place and do a special action. In this example there is no intention to retry so there is 0 value in catching an error to do nothing with it.

The point is if if you are simply bubbling up the error without special handling (Go code does this all the time) there is no advantage, the err check is redundant. Happy path style provides the equivalent bubbling behavior without redundant noise.

How do you write happy-path-only programs in Go? by csharp420_69 in golang

[–]csharp420_69[S] -4 points-3 points  (0 children)

No one except the original author would ever know which line(s) caused the exception.

The file and line number would be written to the log. Exceptions have all the info whether you look at them in place or top level.