All language stacks get faster over time and redeploying with a new version can improve performance and save costs. Nothing exclusive to Golang about that.
Edit - I'm curious as to what is so controversial about this comment? What stack has gotten slower over time?
Python 3 was slower than the preceding Python 2.x version, I believe; it's getting more difficult to find sources now (it's been a while) but there's plenty of resources that showed Python 3 had performance regressions.
Python 3 is the most striking example but it's unfortunately common in Python. Generators were originally slower than list comps, so after their introduction some modules got slower as they switched internally. super was slower than explicit parent calls, and some modules switched even if they didn't immediately need the fancier MRO. True and False were slightly faster when they were alternate names for 1/0 rather than a different type due to various fast-paths for int handling. I think before 2.3 old-style classes were still faster than new-style classes in some cases, but my memory is pretty fuzzy.
It's not so much about it getting faster over time, it's about it NEVER getting slower, and never breaking.
That is something you want to have in your stack when you want to stay on latest and not get stuck on a rewrite 6 years down the road because it's TCO-slow and incompatible with the latest-and-greatest version that is finally fast.
Look no further than python and node to find languages that have notable performance regressions on new releases over time.
No popular language is interpreted these days outside of some specific domains where you might have an embedded scripting environment. But for all the major runtimes, even the JIT languages are now compiled.
This has been true for as long as Go has existed too.
Er. Python is one of, if not the most popular languages on the planet, and its official/primary implementation is only just now moving towards JIT, having been interpreted for the previous 3 decades.
It’s still compiled to byte code. It’s not been interpreted in decades.
I’ll accept that the definition of “interpreter” is a little more blurred these days now that pretty much all JIT languages compile to byte code. But I’d argue that the byte code is typically closer to machine code than it is source and thus the only languages that are common place and truly still fully interpreted are shell scripts (like Bash).
>>> Go will generally be vastly faster than interpreted languages regardless of versions.
Python is clearly in the latter group.
As for,
> byte code is typically closer to machine code than it is source
I would very much like to see some evidence; last I'd seen, it was basically tokenized source code and that's it, still nowhere near anything resembling binary executable code.
It’s compiled to a stack machine. Exactly like how a lot of AOT compiled languages originally used byte code too. Oddly enough they were never called “interpreted” languages. Which means the difference here isn’t the tech stack / compilation process but rather at what stage the compiler is invoked.
> But it seems people have warped “interpreted” to mean JIT to compensate for the advancements in scripting runtimes. That is a bastardisation of the term in my opinion.
Python's not JIT, either. It reads bytecode - which AFAIK is just the source code but tokenized - and it runs it, one operation at a time. It doesn't compile anything to native CPU instructions.
That’s the 2nd time you’ve posted that and it wasn’t right the first time you said it either.
CPython’s virtual machine is stack based thus the byte code is more than just tokenised source code.
In fact there’d be very little point just tokenising the source code and interpreting that because you get no performance benefit over that vs running straight off the source. Whereas compiling to a stack machine does allow you to make stronger assertions about the runtime.
One could argue that the byte code in the VM is interpreted but one could also argue that instructions in a Windows PE or Linux ELF are interpreted too. However normal people don’t say that. Normal people define “interpreted” as languages that execute from source. CPython doesn’t do this, it compiles to byte code that is executed on a stack machine.
Hence why I keep saying the term “interpreted” is misused these days.
Or to put it another way, Visual Basic and Java behaved similarly in the 90s. They compiled to P-Code/byte code that would execute inside a virtual machine and at that time the pseudo code (as some called it - not to be confused with human readable pseudo code but technically “pseudo” is what the “P” stands for in “P-code”) was interpreted instruction by instruction inside a stack machine.
Those languages were not classed as “interpreted”.
The only distinction between them and CPython is that they were AOT and CPython is JIT. And now we are back to my point about how you’re conflating “interpretation” with “JIT”.
>The only distinction between them and CPython is that they were AOT and CPython is JIT. And now we are back to my point about how you’re conflating “interpretation” with “JIT”.
Talking about conflating though, AOT and JIT mean different things in a programming context...
I’m not conflating the terms AOT and JIT. I’m using examples from how AOT compilers work to illustrate how modern JIT compilers might have passes that are described as an interpreter but that doesn’t make the language an interpreted language.
Ie many languages are still called “interpreted” despite the fact that their compiler more or less functions exactly the same as many “compiled languages” except rather than being invoked by the developer and the byte code shipped, it’s invoked by the user with the source shipped. But the underlying compiler tech is roughly the same (ie the language is compiled and not interpreted).
Thus the reason people call (for example) Python and JavaScript “interpreted” is outdated habit rather than technical accuracy.
Edit:
Let’s phrase this a different way. The context is “what is an interpreted language?”
Python compiles to byte code that runs on a stack machine. That stack machine might be a VM that offers an abstraction between the host and Python but none the less it’s still a new form of code. Much like you could compile C to machine code and you no longer have C. Or Nim to C. Or C to WASM. In every instance you’re compiling from one language to another (using the term “language in a looser sense here”).
Now you could argue that the byte code is an interpreted language, and in the case of CPython that is definitely true. But that doesn’t extend the definition backwards to Python.
The reason I cite that the definition cannot be extended backwards is because we already have precedence of that not happening with languages like Java (at least with regards to its 90s implementation. I understand things have evolved since but not poked at the JVM internals for a while).
So what is the difference between Java and Python to make this weird double standard?
The difference is (or rather was) just that JIT languages like Python used to be fully interpreted and thus lazily still referred to that way. Where as AOT languages like Java were often lumped in the same category as C (I’m not saying their compilation process is equivalent because clearly its not. But colloquially people to often lump them in the same group due to them both being AOT).
Hence why I make comparisons to some AOT languages when demonstrating how JIT compilers are similar. And hence why I make the statement that aside from shell scripting, no popular language is interpreted these days. It’s just too damn slow and compilers are fast so it makes logical sense to compile to byte code (even machine code in some instances) and have that assembled language interpreted instead.
Personally (as someone who writes compilers for fun) I think this distinction is pretty obvious and very important to make. But it seems to have thrown a lot of people.
So to summarise: Python isn’t an interpreted language these days. Though it’s runtime does have an interpretation stage, it’s not interpreting Python source. However this is also true for some languages we don’t colloquially call “interpreted”.
That’s property of the JIT compiler though, not a lack of compilation. You want to keep compiler times low so you only analyse functions on demand (and cache the byte code).
If CPython behaved identical to Javas compiler people would moan about start up times.
Some AOT languages can mimic this behaviour too with hot loading code. Though a lot of them might still perform some syntax analysis first given that’s an expectation. (For what it’s worth, some “scripting languages” can do a complete check on the source inc unused functions. Eg there’s an optional flag to do this in Perl 5).
I will concede things are a lot more nuanced than I perhaps have credit for though.
You are being confidently incorrect and making sweeping generalizations and assumptions. The vast majority of anything Javascript is still being interpreted (or as you mentioned going through a JIT interpreter), which represents all browsers and a lot of apps.
You’d have more luck if you exampled Bash scripting :P
If you want to be pedantic then these days even those languages that were considered “interpreted” have had their interpreters rewritten to produce byte code that is generally closer to machine code than it is the original source. So they’ve grown beyond the original definition of “interpreted” and evolved into something much closer to a compiled languages.
So if think it’s disingenuous to still call them “interpreted”. And in the case of JavaScript (your example), the largest runtime in use is most definitely a compiler considering it spits out machine code. So that’s definitely not interpreted like you’ve claimed.
It contains a byte code compiler. The point is that one of the stages of the compiler rather than the byte code being interpreted at runtime.
In the link I posted:
> V8 first generates an abstract syntax tree with its own parser.[12] Then, Ignition generates bytecode from this syntax tree using the internal V8 bytecode format.[13] TurboFan compiles this bytecode into machine code. In other words, V8 compiles ECMAScript directly to native machine code using just-in-time compilation before executing it.[14] The compiled code is additionally optimized (and re-optimized) dynamically at runtime, based on heuristics of the code's execution profile. Optimization techniques used include inlining, elision of expensive runtime properties, and inline caching. The garbage collector is a generational incremental collector.[15]
Emphasis mine.
So no, it is not an interpreter. It is definitely a compiler.
It seems people today are confusing “interpreter” with “just in time”…
You're right, ignition does generate bytecode from the AST; it also interprets it.
> With Ignition, V8 compiles JavaScript functions to a concise bytecode, which is between 50% to 25% the size of the equivalent baseline machine code. This bytecode is then executed by a high-performance interpreter which yields execution speeds on real-world websites close to those of code generated by V8’s existing baseline compiler.
You can also find this information on the Wikipedia article you linked to
> In 2016, the Ignition interpreter was added to V8 with the design goal of reducing the memory usage on small memory Android phones in comparison with TurboFan and Crankshaft. Ignition is a Register based machine and shares a similar (albeit not the exact same) design to the Templating Interpreter utilized by HotSpot.
> In 2017, V8 shipped a brand-new compiler pipeline, consisting of Ignition (the interpreter) and TurboFan (the optimizing compiler).
Here lies the problem. Just because a component of the v8 compiler is called an “interpreter” it doesn’t mean that JavaScript (via v8) is an interpreted language.
Which is the point I’m making. Back in the 80s and 90s scripting languages often had no compilation stage aside maybe building an AST and were pretty much 100% interpreted. Anything that ran from byte code was considered compiled (like BASIC vs Java, VB6). Interpreters often ran line by line too.
These days most scripting languages that were traditionally interpreted languages run more like Java except compiled JIT rather than AOT.
But it seems people have warped “interpreted” to mean JIT to compensate for the advancements in scripting runtimes. That is a bastardisation of the term in my opinion. But when you look through this thread you’ll see the same error repeated over and over.
Go isn’t run through an optimising runtime either. Plenty of compiled languages aren’t. However Python code is still transpiled to stack based byte code that runs on a virtual machine.
If you want to get pedantic then that byte code is interpreted but frankly where do you draw the line, are retro console emulators interpreters since they translate instructions from on architecture to another in much the same way? Technically yes but we don’t like to describe them in that way.
This is why I keep saying term “interpreted language” used to mean something quite specific in the 80s and 90s but these days it’s pretty much just slang for “JIT”.
> "There is a limit to optimization, and that ends with optimized machine code."
There's a vast amount of concepts when it comes to "optimization" and we are very far from the limit. There's still tons of research just to improve compiler intelligence for high-level code intention, let alone in runtime performance.
> "Go being a compiled language"
All code is eventually machine code. That's the only way a CPU can execute it. Virtual machines are an implementation detail and the latest versions with dynamic tiered JITers, POGO, vectorization, and other techniques can match or exceed static compilation because they optimize based on actual runtime behavior instead of assumptions; balancing memory safety, fast startup, amortized optimization, and steady state throughput.
> "As far as cloud costs are concerned, a well developed program should cost least with go."
This is meaningless without context. I've built 3 ad exchanges running billions of requests per day with C#/.NET which rivaled other stacks, including C++. I also know several fintech companies that have Java in their performance critical sections. It seems you're working with outdated and limited context of languages and scenarios.
> There's a vast amount of concepts when it comes to "optimization" and we are very far from the limit. There's still tons of research just to improve compiler intelligence for high-level code intention, let alone in runtime performance.
True. I agree. But, take a program (add two numbers, and print the result), and implement it in C / Go / Python / C#. You will find that, the most optimized program in each language, will generate different outputs, machine code wise.
While the C & Go will generate binaries that have X processor instructions, Python & C# will generate more than X.
And there, you have the crux of the issue. Python & C# require runtimes. And those runtimes will have an overhead.
> Virtual machines are an implementation detail
Sorry, I think you have the wrong idea. A VM, is an implementation detail that gets carried to the execution. A VM is the runtime that runs on top of the original runtime(tm) that is the OS. A Go / C program will run directly on the OS. Adding a layer of runtime means reduced performance.
> It seems you're working with outdated and limited context of languages and scenarios.
That just reeks of arrogance.
My point, and the point made by the original comment author is not that C# / Java / Python cannot run billions of requests, its that when you compare cloud charges for running those billions of requests, costs will be less for programs produced by Go / C (and C++ too)
> "Python & C# require runtimes. And those runtimes will have an overhead."
Golang also has a runtime with memory safety, garbage collection, and threading. It's just linked into the native executable and run alongside without a VM but still present.
> "That just reeks of arrogance... costs will be less for programs produced by Go / C (and C++ too)"
You claimed that costs would be the least with Go without any other context. This is neither related to my point (that all stacks get faster over time) nor accurate since other languages are used in similar environments delivering the same or better performance and costs. Again this comes down to "optimization" being far broader than just the language or compilation process.
> Sorry, I think you have the wrong idea. A VM, is an implementation detail that gets carried to the execution. A VM is the runtime that runs on top of the original runtime(tm) that is the OS. A Go / C program will run directly on the OS. Adding a layer of runtime means reduced performance.
After the bytecode has been JIT compiled it runs as close to the metal as a Go program. If you want fast startup you can even compile the code ahead of time and there is absolutely no difference. Apart from .NET not insisting on its own calling convention, which means it has far less overhead when interacting with external libraries than Go.
This whole thread is childish and pointless. Literally 14 year old me would have recognised these arguments and relished getting stuck into them.
I've seen a stack built almost entirely in TCL which handled 10,000 transactions per second for a FTSE 100 company. If that's possible then language is just one choice in the infinite possibilities of production stacks.
> C program will run directly on the OS. Adding a layer of runtime means reduced performance.
A C program that isn't compiled in an embedded context has a runtime layer on top of it. The OS doesn't call your main, it runs the init function of the platforms C runtime to initialize all the bloat and indirection that comes with C. Just like your compiled C program doesn't just execute the native sqrt instruction but runs a wrapper that sets all the state C expects, like the globally visible errno value no one ever checks but some long dead 1980s UNIX guru insisted on. C is portable because just like python and C# it too is specified on top of an abstract machine with properties not actually present in hardware. If you really set out to optimize a C program at the low level you immediately run into all the nonsense the C standard is up to.
JIT compilers run machine code without any overhead, when it comes to actual execution. And the overhead of the compilation can be pushed to another thread, cached, etc.
> Virtual machines are an implementation detail and the latest versions with dynamic tiered JITers, POGO, vectorization, and other techniques can match or exceed static compilation because they optimize based on actual runtime behavior instead of assumptions.
In theory, practice is like theory. In practice, it isn't.
Maybe in 20 years, JITs can provide AOT-like throughput without significant memory / power consumption. Not with current tech, except on cherry picked benchmarks.
Note that akka is currently second for single core, and fastest with multicore. .Net has a nice result, but lets use up to date results. Especially when it still fits your argument that JIT can compete in benchmarks with suitable warmup, etc..
In addition Go is garbage collected. So you have a latency there as well. However, in most Go apps, you don't use that much memory for the garbage collector to become an issue.
Rust does not have a GC, but it's a much more complex language, which causes its own problems. IMHO, Go has the upper hand in simplicity. However, go also limits you a little, because you can't write memory unsafe code by default. You can't do pointer magic like you do in C. But things also get complicated in Go when you start to use concurrency. Again IMHO, event driven concurrency with callback functions (as in nodejs) is easier to wrap your head around then channels.
I don't think this comment deserves being downvoted much. It states an important issue: In simplicity Go might win, but practically you might be better off learning Rust and have the compiler warn you about issues with your concurrency usage for any non-trivial problem.
I don't agree with the "callback functions is easier" part at all though. It takes a bit of wrapping ones head around channels and so on, but when understood, I think they are easier to handle than all those callbacks.
> But things also get complicated in Go when you start to use concurrency.
Things always get complicated when concurrency is involved. And Go handles this much better than most other languages, with language primitives (go, select, chan) dedicated to the task, and CSP being much easier to handle than memory sharing.
> event driven concurrency with callback functions (as in nodejs) is easier to wrap your head around then channels.
How so?
The code becomes non-linear, what bit of code is executed when and how its synchronized is suddenly up to the event loop, and entire libraries suddenly have to be written around a concept (cooperative multitasking) which OS design abandoned in the early 90s for good reason.
And this isn't even taking into account that the whole show is now much harder to scale horizontally, or that it is only suitable for io intensive tasks.
From a self-taught programmer's perspective, you mostly write concurrent code to speed-up io. Writing code with callbacks gives me the illusion of linearity. I know it's prone to errors but for simple tasks it's good enough. That's the reason for nodejs's popularity I think.
CSP is a brilliant concept, but it was a bit hard for me to wrap my head around without diving into graduate level computer science topics. This is not Go's fault, concurrency itself is complicated.
Whenever I try to write concurrent code with go, I resort back to using locks, waitgroups etc. I couldn't make the switch from thinking linearly to concurrently yet.
I might be downvoted again :), but actor based languages seem much more suitable for concurrency for me.
Again, I'm out of programming for a while, the last I coded it was a pain to do network requests concurrently in Rust due to needing intrusive libraries. Python was hard because the whole syntax changed a couple of times, you had to find the right documentation with the right version etc. Go was surprisingly easy, but as I usually plan the program as I write it, trying to write non-trivial concurrent code always results in me getting unexpected behaviour or panics.
The usual material you find on concurrency and parallelism teaches you locks/semaphores using Java, these are easier to wrap your head around. The last I checked material on CSP was limited to graduate level computer science books and articles.
I originally got into Go, because I thought it is memory safe, the code I write will be bulletproof etc. Then I understood that the achilles heel of Go (or any procedural-at-heart programming language) was dynamic memory allocation paired with concurrency.
This is my view as a self-taught programmer having no formal training in computer science. YMMV
Millions-per-second of what? That's a serious load for any stack and depends on far more than just the language.
And it can also be done by other stacks as clearly seen in the TechEmpower benchmarks. Here's .NET and Java doing 7 million HTTP requests/second and maxing out the network card throughput: https://www.techempower.com/benchmarks/#section=data-r20&hw=...
> Optimized native like C++/Rust could handle millions per second without blinking an eye
Keyword: optimized. Almost anything we have now would handle as much as "native optimized".
And no, neither C++ nor Rust would handle millions (multiple) without blinking because the underlying OS wouldn't let you without some heavy, heavy optimizations at the OS layer.
> Languages like Python, Java, C#, are run on virtual machines, meaning you can optimize them only so far.
A virtual machine is just an intermediate language specification. C, C++, and Rust also "run on a virtual machine" (LLVM). But I guess you mean the VM runtime, which includes two main components: a GC, that the Go runtime also includes, and a JIT compiler. The reason Java gives you better performance than Go (after warmup) is that its GC is more advanced than Go's GC, and its JIT compiler is more advanced than Go's compiler. The fact that there's an intermediate compilation into a virtual machine neither improves nor harms performance in any way. The use of a JIT rather than an AOT compiler does imply a need for warmup, but that's not required by a VM (e.g. LLVM normally uses AOT compilation, and only rarely a JIT).
AOT vs JIT compilation is not really that important of a difference to steady state performance. It does impose other performance costs though.
For example, C# and, even more so, Java, are poorly suited to AWS Lambda-style or CLI style commands, since you pay the cost of JIT at every program start-up.
On the other hand, if you have a long-running service, Java and .NET will usually blow Go out of the water performance wise, since Go is ultimately a memory managed language with a very basic GC, a pretty heavy runtime, and a compiler that puts a lot more emphasis on compilation speed than advanced optimizations.
Go's lack of generic data structures, and particularly its lack of covariant (readonly) lists, can lead to a lot of unnecessary copying if not specifically writing for performance (if you have a function that takes a []InterfaceA, but you have a []TypeThatImplementsInterfaceA, you have to copy your entire slice to call that function, since slices are - correctly - not covariant, and there is no readonly-slice alternative that could be covariant).
So Go's runtime is pretty heavy in comparison to... .NET and the JVM? Or pretty heavy in general? This is the first I would be hearing either of these two opinions in either case, so interested in the rationale.
It's pretty heavy compared to C and C++, not .NET or JVM.
The point is that Go, even if AOT compiled, is much nearer to .NET and JVM (and often slower than those two) than to C or C++.
To make that least clearer: it's generally slower than C or C++ because the compiler is not doing very advanced optimizations, the runtime is significantly heavier than them, and GC use has certain unavoidable costs for certain workloads.
Go is generally slower than .NET or Java because its AOT compiler is not as advanced as their JIT compilers, it's GC is not tunable and it's a much more primitive design, and certain common Go patterns are known to sacrifice speed for ease of use (particularly channels).
> There is a limit to optimization, and that ends with optimized machine code.
There might be a low limit, but there’s no high limit: you can generate completely unoptimised machine code, and that’s much closer to what Go’s compiler does.
> Go being a compiled language, that is cross paltform but separate binaries, should be very close to C in terms of performance.
That’s assuming that Go’s optimisation pipeline is anywhere near what mainstream C compilers provide. Against all evidence.
Just check .NET Framework 4 to .NET Core transition. You can dig MSDN blogs. While Go should be lauded for their performance improvements, it should not be done by reducing other languages/stacks’ achievements.
While that’s what they’d teach you in CS it’s missing enough nuance to be incorrect.
> There is a limit to optimization, and that ends with optimized machine code.
While that’s true, you overlook the cost of language abstractions. I’m don’t mean runtime decisions like any use of byte code but rather the language design decisions like support for garbage collection, macros, use of generics and/or reflection, green threads vs POSIX threads and even basic features like bounds checking, how strings are modelled and arrays vs slices.
These will all directly impact just how far you can optimise the machine code because they are abstractions that have a direct impact to the machine code that is generated.
> Go being a compiled language, that is cross paltform but separate binaries, should be very close to C in terms of performance.
No it shouldn’t. C doesn’t have GC, bounds checking, green threads, strings, slices, nor any of the other nice things that makes life a little easier as a modern developer (I say this not as a criticism of C, I do like that language a lot but it suits a different domain to Go).
> Languages like Python, Java, C#, are run on virtual machines, meaning you can optimize them only so far.
Aside from CPythons runtime, their instructions still compiles to machine code.
These days no popular language, not even scripting ones, interprets byte code upon execution. [edit: I posted that before drinking coffee and now realise it was a gross exaggeration and ultimately untrue.]
You’ll find a lot of these languages compile their instructions to machine code. Some might use an intermediate virtual machine with byte code but in a lot of cases that’s just part of the compiler pass. Some might be just in time (JIT) compiled, but in many cases they’re still compiled to machine code.
> As far as cloud costs are concerned, a well developed program should cost least with go.
> Go is best suited for running intensive compute loads in serverless environments.
That’s a hugely generalised statement and the reality is that it just depends on your workloads.
Want machine learning? Well then Go isn’t the best language. Have lots of small queries and need to rely on a lower start up time because execution time is always going to be short, we’ll node will often prove superior.
Don’t get me wrong, Go has its place too. But hugely generalised statements seldom work generally in IT.
Yeah, now I think about it, plenty of runtimes do. I have no idea why I posted that when I know it’s not true. Must have been related to the time of the morning and the lack of coffee.
> Languages like Python, Java, C#, are run on virtual machines, meaning you can optimize them only so far.
A JIT compiler can optimize _more_ than an AOT one can. It has way more profile guided data and can make assumptions based on how the live application is being used.
Edit - I'm curious as to what is so controversial about this comment? What stack has gotten slower over time?