Live data from Hacker News

Practices for writing high-performance Go

github.com

51–60 of 102 posts

Re: Practices for writing high-performance Go

#51
post #45

Earlier quoted context omitted.

Disagree. I can write a server in C++ that, with careful thought and a bit of planning, will be able to exploit the resources of the kind of 88-core dual-socket machines that are mainstream today. No amount of planning will allow me to write a Go program that does the same thing on the same machine. Small amounts of concurrency might seem easy in Go but lots of concurrency is hard.

Could you describe how you would go about designing this and what libraries/frameworks would you use ? AFAIK co-routines is only coming with C++ 20, so I guess you would still use std::thread right ?

std::thread has only been around since C++11, so most existing HPC codebases probably either use pthreads[1] directly, or an organization- or project-specific library wrapping pthreads. Some used boost::Thread, which was effectively the predecessor of std::thread.

If you want to saturate every core on a very parallel problem like "handle as many packets as you can", a very rough first approach would be to spin up 88 threads (one per core). Within each thread, you could use either something like boost::Fiber[2] (similar to goroutines, except mapped N:1 to OS threads, rather than M:N) to avoid blocking the threads on IO. This paper[3] has a good overview of different concurrency approaches.

If you're doing something that's not embarrassingly parallel[4], then often there is a well-researched approach for your specific domain. The same general ideas apply to keeping your cores busy, but you're often more bounded by communication between threads, memory bandwidth, etc.

[1] https://en.wikipedia.org/wiki/POSIX_Threads

[2] https://www.boost.org/doc/libs/1_70_0/libs/fiber/doc/html/fi...

[3] http://www.sosp.org/2001/papers/welsh.pdf

[4] https://en.wikipedia.org/wiki/Embarrassingly_parallel

Re: Practices for writing high-performance Go

#52

Earlier quoted context omitted.

All over my profiler. Go can deal with something embarrassingly parallel, same as any other language, but try getting it to scale up to high core counts with small RPCs is impossible, for the reasons the OP mentioned: you can't avoid its global allocator and you don't control the scheduler either. You will spend all of your CPU time in runtime.findrunnable and runtime.mallocgc.

You will spend all of your CPU time in runtime.findrunnable Profile your channel latency, then design around that. "Small RPCs?" With an 88 core server, should you be making that many remote procedure calls? Find the 20% most intensive code and take that out of the hands of the scheduler, as much as possible. and runtime.mallocgc Even with sync.Pool? Can you design those parts of the system to mostly use the stack?

Not for nothing but channels become a bottleneck pretty early when you start doing highly concurrent golang.

You can very carefully design around it using only channels but it quickly starts making more sense to just use a different concurrency abstraction.

Re: Practices for writing high-performance Go

#53
post #33

Earlier quoted context omitted.

Usually I use pools to avoid the allocation cost. The GC sweep is not usually my problem.

Profile it and see if it is a problem in Go, I would say. I don't know if the Go runtime has optimizations for this kind of thing inherently, and it might. Profile, and see.

That's ... not helpful. I already profile and profiling indicates that allocations are slow and sweeps are fast.

Go's optimization for this is escape analysis which is a best-effort attempt to put things on the stack. Because the stack analysis is naive (which isn't necessarily a bad thing), it means lots of things are still allocated on the heap, and those are the allocations I'm referring to.

Re: Practices for writing high-performance Go

#54
post #53

Earlier quoted context omitted.

Profile it and see if it is a problem in Go, I would say. I don't know if the Go runtime has optimizations for this kind of thing inherently, and it might. Profile, and see.

That's ... not helpful. I already profile and profiling indicates that allocations are slow and sweeps are fast. Go's optimization for this is escape analysis which is a best-effort attempt to put things on the stack. Because the stack analysis is naive (which isn't necessarily a bad thing), it means lots of things are still allocated on the heap, and those are the allocations I'm referring to.

The bigger issue is usually stack vs heap allocation and it’s hard to reason about that. But the good news is that unlike a JIT system you can actually pretest golang escape analysis.

Look at the -m gcflag to see what is escaping, then see if that is where the allocation hotspot is. If so see if you can get the escape to happen where you want.

Otherwise you need to go no allocation. Notice that go actually gets _worse_ over time generally at heap allocation so it’s likrly worse than early profiles suggest.

Re: Practices for writing high-performance Go

#55
post #40

Earlier quoted context omitted.

Disagree. I can write a server in C++ that, with careful thought and a bit of planning, will be able to exploit the resources of the kind of 88-core dual-socket machines that are mainstream today. No amount of planning will allow me to write a Go program that does the same thing on the same machine. Small amounts of concurrency might seem easy in Go but lots of concurrency is hard.

"I can write a server in C++ that, with careful thought and a bit of planning, will be able to exploit the resources of the kind of 88-core dual-socket machines that are mainstream today." This is downvoted grey as I write this, and as someone who has been called a "Go shill" on occasion... it's exactly right. Go is a decent language for writing code in a fairly straightforward manner and getting pretty good performa…

As a fellow gopher, I agree, Go isn't trying to compete with C/C++ on performance, it's trying to give you safety and ease of use of high level language.

I think the point about concurrency typifies this, channels aren't as fast as mutexs and semaphores, but it makes sharing code with co workers easier to reason about.

If you're in a domain where performance is still king, Go isn't trying to find a place there.

Re: Practices for writing high-performance Go

#56

Earlier quoted context omitted.

Hmm, that seems a bit exaggerated. I seem to recall someone on the Go team describing the rewrite of a server that was originally in C++ and became much faster. (Largely because it started out at a heap of legacy code that was neglected for years.) That's probably pretty unusual, but the point is, just because it was written in C++ doesn't mean it's any good. Not everything gets serious attention from people who know…

You are thinking of dl.google.com. It was a Go program that replaced a very old single-threaded C++ server (using the ancient SelectServer C++ core, deprecated at that time). The thing you have to realize about Google infrastructure is it does not require vertical scalability of its service backends. It is very typical to write a program and deploy it with 100 replicas having one CPU each on 100 different machines. A…

Apples and oranges, though still an important lesson: many optimizations happen at the level of interactions between services. C++ is more micro-optimizable than Go, but it doesn't matter when the low hanging fruit has nothing to do with language (and I would argue this is more often than not).

Re: Practices for writing high-performance Go

#57
post #43

Earlier quoted context omitted.

Everyone I met there had my respect. I find "not capable of understanding a brilliant language" to be baseless contempt and I'm surprised he didn't get more heat over it. I wouldn't work with him, nor whatever people he's trying in vain to accommodate.

That's really quite ironic given: https://news.ycombinator.com/item?id=19825787

I should have worded that more gently. The greatest programmers in the world have demonstrated that even they can't use C reliably.

Re: Practices for writing high-performance Go

#59

Earlier quoted context omitted.

You will spend all of your CPU time in runtime.findrunnable Profile your channel latency, then design around that. "Small RPCs?" With an 88 core server, should you be making that many remote procedure calls? Find the 20% most intensive code and take that out of the hands of the scheduler, as much as possible. and runtime.mallocgc Even with sync.Pool? Can you design those parts of the system to mostly use the stack?

Not for nothing but channels become a bottleneck pretty early when you start doing highly concurrent golang. You can very carefully design around it using only channels but it quickly starts making more sense to just use a different concurrency abstraction.

My experience has been channels are a really good way to make highly concurrent code readable for people who hate concurrency (ymmv).

They aren't as efficient, but using them as a semaphore (i.e. only signaling) can facilitate fairly-efficient, easier-to-read shared memory. But I'm kind of proving the whole...

> You can very carefully design around it only channels

...part of your post.

Re: Practices for writing high-performance Go

#60
post #30
post #22

Earlier quoted context omitted.

You're talking a about a very specific scenario, out of the box Go is off course easier than C++ for concurrency. Who by the way runs applications on a 88 core server, why would you so they instead of splitting that into smaller chunks.

> Who by the way runs applications on a 88 core server, why would you so they instead of splitting that into smaller chunks. Graph analysis and/or routing, for one. Distributed Dijkstra is pretty impractical (the optimal lower bound on the number of messages required equals the number of edges in the graph!) For example, I don't work for Google, but I can pretty much guarantee you that an individual Google Maps routi…

I think you're ignoring caching/preprocessing in your analysis.
Post reply on HN