Live data from Hacker News

Making a Go program faster with a one-character change

hmarr.com

241–249 of 249 posts

Re: Making a Go program faster with a one-character change

#241
post #236
post #217

Earlier quoted context omitted.

>and this kind of leak has side effects Only if the calling function does something with the pointer (which it generally won't, if err is non-nil). If the calling code does do something with the pointer even when err is non-nil, then either (a) the value of the pointer is meaningful, and this is fine; or (b) there's a logic error in the calling code, which is a far more serious bug than a potential memory leak. So I…

I should have been more explicit. Go is garbage collected . If you keep a pointer live, then it’s not garbage , which prevents it from being collected. At least Go doesn’t use RAII, so inadvertently pinning a pointer won’t keep a socket open or a lock held.

Just returning the pointer doesn't keep the value that it points to live, unless the calling function does something with the pointer.

The following toy example doesn't leak memory. Are you thinking of some other pattern that would leak memory? If so, what is it? I can only think of patterns where a logic error is also involved. That is, where the meaningless value of 'val' is used in code paths where err != nil.

    func alwaysError() (*int, error) {
        var dummy int

        // We return &dummy even though it's a meaningless value.
        // Will this cause a memory leak?

        return &dummy, fmt.Errorf("An error")
    }

    func caller() {
        val, err := alwaysError()
        if err != nil {
            fmt.Printf("Error\n")

            // It won't! Because the value pointed to by 'val'
            // can be GCed from this point on.
            return
        }

        // never get here        

        fmt.Printf("Value %v\n", val)
        //
        // ...lots of code that uses *val...
        //
    }
You might think that you'd get a leak if the error branch was also long-lived, but Go's GC seems to be precise with respect to conditional branching (as far as I can tell by experimenting with runtime.SetFinalizer). That is, as long as the error branch doesn't refer to 'val', then the value pointed to by 'val' can be collected once the error branch is entered (and before 'caller' returns).

Re: Making a Go program faster with a one-character change

#242

Earlier quoted context omitted.

I'm a dumb dumb. Can you define "implicit reference semantics" and "value semantics"? You use the phrases several times in your post, but I don't really understand what you mean. If it helps, I'm not a C++ programmer, but I am familiar with higher level languages like Go, Python, Ruby, PHP and Javascript.

“Implicit reference semantics” means that variables ordinarily refer to objects rather than containing them. “Value semantics” means that variables contain values rather than references, though there are pointer types that let you explicitly store references when you want them. (Often this is discussed in terms of parameter passing, for historical reasons, but the same ideas apply to variables more generally.) If you…

Excellent, clear answer. Thank you.

Re: Making a Go program faster with a one-character change

#243

Aaaaaand that's why I love Rust's decision to make copies explicit with `.clone()`. Annoying as hell when you're not used to it but overall worth it.

Except a lot of structs also derive and prefer `Copy` and a lot of rust code also avoids heap allocation which requires `Clone`. The `Copy` trait can be used implicitly like in the example here. On the other hand, due to the lack of garbage collector, you wouldn't be able to return the reference to the copy which might lead you to find your accidental copy.

The Copy trait can only be used for bitwise copies. Expensive copying with heap allocations will never happen implicitly

Re: Making a Go program faster with a one-character change

#244
post #134

Earlier quoted context omitted.

The one place where you’ll actually see this is in Read(). If you try to read 1000 bytes, you might get “800 bytes read, EOF” as a result. The worst part is that os.File won’t ever do this!

I really hate when people use "errors" for signals. Or, alternately, use the signals mechanisms for actual errors. An error should be "something went wrong", and reading a file to the EOF is not "going wrong", that's just what the "read()" should do if you tell it so! I like Go's multiple returns and error checking by default, but it definitely should have been implemented with some sort of "Result/Error" type union…

But that's what "errors" are, the're signals.

Re: Making a Go program faster with a one-character change

#245
post #241
post #236

Earlier quoted context omitted.

I should have been more explicit. Go is garbage collected . If you keep a pointer live, then it’s not garbage , which prevents it from being collected. At least Go doesn’t use RAII, so inadvertently pinning a pointer won’t keep a socket open or a lock held.

Just returning the pointer doesn't keep the value that it points to live, unless the calling function does something with the pointer. The following toy example doesn't leak memory. Are you thinking of some other pattern that would leak memory? If so, what is it? I can only think of patterns where a logic error is also involved. That is, where the meaningless value of 'val' is used in code paths where err != nil. fun…

I haven't actually played with this, but if the same pattern of lazily (sloppily?) returning arbitrary pointers along with errors continues, the lifetime ought to be extended arbitrarily. The caller could itself return val, err, and so on up the chain.

Re: Making a Go program faster with a one-character change

#246

Earlier quoted context omitted.

Go compiler is garbage by the design. A 20 year old C compiler does not have this prob. This is also why Go have declined so much during the last couple of years. The benefits of Go have not increased and most of the quirks are still there. Like the error handling, the naive compiler and the syntax sugar that somewhat hides the diff between pointers and direct heap allocs. -1

I work on a code base that is a mixture of Go and C. It's IO, CPU and Memory hungry, and it's distributed. C is fast because it's close to how CPU and memory actually work. Go gives you 95+% of that plus easy to learn, easy to use language. A new person could start contributing useful features and bug fixes immediately. A senior person could get C-level performance. More and more of our code is moved from C to Go, wi…

I do prefer Go over C in most cases and it's not like there are no pitfalls in C. It is just disappointing when a lang doesn't improve over time and have the same stupid problems year after year.

Re: Making a Go program faster with a one-character change

#247
post #245
post #241

Earlier quoted context omitted.

Just returning the pointer doesn't keep the value that it points to live, unless the calling function does something with the pointer. The following toy example doesn't leak memory. Are you thinking of some other pattern that would leak memory? If so, what is it? I can only think of patterns where a logic error is also involved. That is, where the meaningless value of 'val' is used in code paths where err != nil. fun…

I haven't actually played with this, but if the same pattern of lazily (sloppily?) returning arbitrary pointers along with errors continues, the lifetime ought to be extended arbitrarily. The caller could itself return val, err, and so on up the chain.

I'm not seeing how that would cause a memory leak. It just means that the value might be GCed a bit later. Could you give an example?

Re: Making a Go program faster with a one-character change

#248
post #247
post #245

Earlier quoted context omitted.

I haven't actually played with this, but if the same pattern of lazily (sloppily?) returning arbitrary pointers along with errors continues, the lifetime ought to be extended arbitrarily. The caller could itself return val, err, and so on up the chain.

I'm not seeing how that would cause a memory leak. It just means that the value might be GCed a bit later. Could you give an example?

It’s a temporary leak. My point is that it’s an actual side effect, unlike how in C it has no effect whatsoever if the value is ignored.

In a language where destruction of an object often releases external resources (C++, for example, or Python or Rust to a lesser extent), this effect would be more dramatic. Imagine someone doing this in Python:

    def func():
      f = tmpfile(…)
      err = do something
      return f, err
This style would be absurd and wrong — it keeps f alive to long.

Re: Making a Go program faster with a one-character change

#249
post #228

Earlier quoted context omitted.

Just curious, isn't it obvious from a logical standpoint? I don't see how one could consider a mutable type to be a subtype of an immutable one. On the other hand, an immutable subtype of a mutable one seem plausible?

Immutable from a mutable is just as implausible. You cannot remove a method from a subtype which results in mutating methods being disabled through other means (like throwing exceptions). This is also a violation of LSP and the open close principle. Consider a `List` with an `add` function. What would you do with that `add` function to make an `ImmutableList` subtype?

Ah I see. Why throw exceptions? They could just be noop depending on the typestate?

But still it doesn't look very nice in practice. I think that in that case, it does make much more sense for a mutable type to be a subtype of the immutable one.

The immutable threw me off track. It's just that the supertype would lack mutation operations. (in a sense immutable would be the default for every type).

Thank you.

Post reply on HN