So whenever people talk about expressiveness of Golang it just seems like a design gone bad. The designers wanted a programming language with the expressiveness of python and the speed of C, they ended up with a language with the expressiveness of C and the speed of python.
I suppose the one thing that Go does well (compared to C++ or Java) is builtin concurrency and communication across tasks.
I'm a Java-literate C/C++ programmer. I would avoid writing straight Java code at all costs; I find it immiserating. Here are some reasons off the top of my head that Golang is more pleasant to work in:
* The syntax is deliberately streamlined, including implicit declarations, semicolon insertion, lightweight expression syntax, the capital-letter-exports-a-symbol thing
* It has fully functional idiomatic closures
* Interfaces get rid of adapter class BS
* The table type (maps, in Golang) is baked into the language, like Python, not a library, like C++ and Java
* Clearer, more direct control over memory layout; data structures in Golang feel like C
I don't know if Golang's standard library is that much better than Java's, but it was obviously designed carefully by and for systems programmers, so I find it remarkably easy to work with.
It also feels like a much smaller system than Java. Almost every time I write a significant amount of Golang code, I find myself answering questions by just reading the standard library source code. It's easy to get your head around. I've written compiler/runtime-level code for the JVM and I still don't have a great grip on all of Java.
Another cool aspect of the last point (Go being small and lightweight) is that if you've got gcc and mercurial on a supported platform, building latest go from source is as easy as:
hg clone http://code.google.com/p.go cd go/src ./make.bash
Got to build a local copy of the JVM and/or JDK for some reason? Good luck with that (even ignoring all the licensing, OpenJDK vs closed, etc)
In other words, it is possible to express anything in one Turing-complete language that is possible to express in another.
So to measure the complexity of a program P we have to write another program G that generates P and then take G's length. It's not a given that a generator for a verbose language is necessarily longer than a generator for a terse language.
But more importantly, what we want to measure is the mental effort required to write and understand code in different languages. To measure that effort in terms of Kolmogorov complexity, we'd have to write a program that generates our states of mind while programming.
Good luck with that ;-)
You have to think of it in terms of compression, because that's what Kolmogorov complexity is really about. It is certainly possible that after compression J1 is shorter than T0.
In terms of this definition we need two Turing machines, one whose transition function can be proven to be the shortest that describes L1. And another one whose transition function can be proven to be the shortest that describes T0. These two Turing machines or transition functions are compressed representations of L1 and T0 respectively.
What I'm saying is that the one describing L1 can be shorter than the one describing T0 even if L1 is longer than T0.
I have not only the transition function but 6 other entities to define to accomplish this (really 5 barring the 'blank symbol') so I am certain that it is not only possible but likely much smaller than one might think at first. With such a small M (that penalizes attempts to compress T0 and J1), and a large T0 [such as the code base for Microsoft Windows ported to F#] and an even larger J1 [such as the same code base written in C], we still see my desired result.
"More formally, the complexity of a string is the length of the shortest possible description of the string in some fixed universal description language".
M is not that universal language. M is a program written in the universal language of Turing machines. I guess that is our misunderstanding. If you define M itself as that universal language but M only ever does that one contrived thing you said (i.e. there is only one program written in that language) then it is not a universal language any longer and any comparison of lengths would become completely pointless.
Then, the question becomes, how much logic do we have to put into M itself to achieve the above? I posit that it would not be very much in fact (but that should be demonstrable if someone were sufficiently motivated).
I think KC requires that M be constant across the texts you wish to compare (so that texts across various languages are normalized to an underlying architecture).
If one views M in this light (as an attempt to level the playing field across competing hardwares/algorithms/languages), then we could see it as the concatenation of the following:
1 - the specification of the raw hardware. 2 - the microcode for the hardware. 3 - BIOS, drivers, HAL, OS, etc. 4 - language implementation/runtime/shared libraries etc.
Now, to compare any two real-world implementations, we need to port them both to the language implemented in #4, for running on the hardware specified by #1-3 and sum their length with M's length.
And if we want to compare two algorithms from different languages (but running on the same 'machine'), we can shift the codes in #4 into the 'texts' of the programs themselves (so as to keep M constant between them). We can do this all the way down to the minimum M required to be Turing complete (and nearly all the way 'up' to the programs under test); i.e., the division between M and a particular text, L0, is somewhat variable.
First of all, I doubt that this is even possible, because the input, i.e L1 and T0 is arbitrarily long in the general case, while the algorithm's length must be determined before it sees any input, i.e it is constant. That's related to the invariance theorem: http://en.wikipedia.org/wiki/Kolmogorov_complexity#Invarianc...
But even if it were possible or if we agree to arbitrarily limit the size of the input for practical purposes, you wouldn't have achieved your original goal, which was to show that a verbose language can never be as expressive as a terse language.
Your universal language is neither a Turing machine nor any other generally accepted universal language, so no one would agree to accept this particular language as a test of Kolmogorov complexity.
If you can disprove the invariance theorem you may have come up with a valid criticism of Kolmogorov complexity itself, but you were the one who initially based his argument about the expressiveness of programming languages on Kolmogorov complexity. If you invalidate Kolmogorov complexity you invalidate your own argument as well.
In any event, this has been a fun thought experiment.
I think I am suggesting to construct a particular universal language designed to make any compression of some reasonable T0 and J1 written in that UL (e.g., the source code of Microsoft Windows written in F# and C respectively) longer than T0 and J1 themselves, hence leaving them to be their own Kolmogorov limit (if you will). This seems entirely doable to me within the confines of the 5-tuples afforded me by the formal definition of a Turing machine (although I can see how others, including you, might be less convinced). :-) Maybe I, or someone else sufficiently motivated, will attempt it someday.
[1] http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.4...
[2] http://gafter.blogspot.com/2007/03/on-expressive-power-of-pr...
"In my mind, a language construct is expressive if it enables you to write (and use) an API that can't be written (and used) without the construct." - Neil Gafter
Once more, there is no such construct. All APIs can be written and consumed without language support. Otherwise, Boost would not have had all the new C++ goodies (as an add-on library) before they became part of the language itself. And, of course, one can always put the 'human compiler' to work to produce boilerplate that would be produced by the compiler if the language in question supported the feature in question (if there isn't an add-on library providing the feature in question). One can always do this but the discriminating one tries to avoid it and instead chooses 'expressive' (i.e., conciseness-facilitating) languages. [In the case of closures in Java, one [human-compiler] would merely use one-off interfaces or anonymous classes].
I will take a look at the other links but I gave up on the commentary after seeing such a blatant falsehood.
Yes.
"* ^^"._? #> (a.!?(1, Get())) must be_==(Full(Answer(1))) ?~! eventually
It is very short, most of the people would implement the same thing using 5-10 lines of code. I prefer 5-10 lines of code over regex-like syntax."Are you sure about that?"
"Yes."
Do I need to explain?
Certainly if you intended to say more than 'yes' then you failed at adequately expressing the message to begin with so comparisons at that point are moot: i.e., two pieces of text must both express precisely the same ideas before comparisons of them make sense.
Of course this only really applies to long running apps (web servers), if startup time matters then certainly Go wins.
Perhaps I need to try Go out, but I just don't see what the selling point is.
In a word: hype. If Go did not have the Google brand name attached to it, it wouldn't have gone anywhere.
I actually did write a fair amount of Go code (my primary languages are C++ and Python).
On real, non-toy programs it is significantly more concise than C++, in the ballpark of Python code.
This isn't visible on code snippets (less than 500 loc) like the toy raytracer, but trust me: you'll see a big difference on 10k loc codebase.
Given that "kkowalczyk" talked about 10K line programs, what applications are you thinking of that are 10K lines and cannot tolerate a 0.1 second/3 MB overhead. Would you restart java everytime? Note that even a simple helloworld c program has about 0.01second/0.5MB overhead.
$ time java HelloWorld
Hello, world!
real 0m0.101s
Touche. 0.1 seconds is exactly right, at least on my setup. That said, javac is slow given the program is 5 lines of code: $ time javac HelloWorld.java
real 0m0.511s
user 0m0.833s
sys 0m0.050s
And I'd ventured to guess that there must be something to the JIT being pretty slow for real-world applications, otherwise people wouldn't complain so frequently about it. Maybe aspects of JIT optimizations increase linearly-ish with the amount/complexity of code?FWIW, we went with go at my work instead of java because our application is memory-intensive, and there's huge gains there in go over java.
Somewhat anecdotally, I remember hearing Rich Hickey talking about how the JVM JIT loves stateless code. I'm pretty sure the vast majority of Java code in use is deeply stateful. I don't know how much of a difference this actually makes but it seemed like a relevant data point.
If you know what crt0.c does, you can see C is also pretty much the asymptote of what you're going to get from program startup, so it's a little silly to make the comparison.
One can always use one of the commercial native compilers for Java, any of them can easily produce static binaries with fast startup times.
Don't measure Java the language, by Oracle JVM the implementation.
Not really. Other languages are far superior in this regard (see Scala or Rust for instance).
"they ended up with a language with the expressiveness of C and the speed of python."
This is just pure troll. Apparently you have a Python variant of this renderer that matches the already impressive Go performance? Please reveal it.
AFAICT, the entire situation started only because the article was submitted to /r/Golang and /r/C++ with the trollbait title "Business Card Ray Tracer: Go faster than C++", and not because of anything in the article itself (which was actually a pretty good article).
I think the majority of languages in popular use are faster than Python. I believe that Go is popular with the Python / Ruby crowd because idiomatic Go is quite close to what they do already. I.e. you don't need to learn much to shift from Python or Ruby to Go. Using a language like Scala, for instance, is a much bigger jump.
C++ has 30 years of history behind it, MS VC++ is 20 years old, Intel's C++ compiler is at least 10 years old, etc.
Go doesn't do SIMD at all (see note 1). Personally I leverage Go coupled with the Intel Compiler (Go happily links with and uses very high performance C-built libraries, where I'm rocking out with SSE3 / AVX / AVX2).
To respond to something that Ptacek said above, many of us do expect Go to achieve C-level performance eventually. There is nothing stopping the Go compiler from using SIMD and automatic vectorization, it just doesn't yet. There is nothing about the language that prohibits it from a very high level of optimization, and indeed the language is generally sparse in a manner that allows for those optimizations.
*1 - For performance critical code you are supposed to use gccgo, which uses the same intermediary as the C compiler, allowing it to do all of the vectorization and the like. Unfortunately for this specific code gccgo generates terrible code, yielding a runtime that is magnitudes slower (albeit absolutely tiny). Haven't looked into why that is.
Those optimizations would almost certainly reduce the speed of the Go compiler (requiring SSA form and aliasing info).
> There is nothing about the language that prohibits it from a very high level of optimization, indeed the language is generally sparse in a manner that allows for those optimizations.
Autovectorization is very sensitive to good output from alias analysis. This is where the const and restrict keywords in C, absent in Go, are useful. I think you will at least need runtime guards in Go, whereas they are not necessary in well-written C.
My point is not about vectorization though. Code that uses mostly vectors, math, and function calls has a very direct translation to machine code. I expect all compilers to generate approximately the same machine code for this type of code, assuming vectorization doesn't come into play. So I don't expect to see large differences in performance. Of course there will be some difference, but not the order of magnitude one sees between compiled (statically or JITed) languages and interpreted languages.
Now that 256 bit AVX registers that process 4 numbers in one go, even when one uses 64bit floats (and 8 with 32bit floats), vectorization more and more comes into play.
Using 64bit floats with 128bit SSE registers, it was kinda possible to ignore the vectorization, as it was less than 2x speedup. But no more.
A JVM could compile byte code to SIMD instructions. Most of them don't, yet.
And of course the reason you code for auto-vectorization is for ease of platform support. The linked AVX code will not run on the vast majority of virtual machines, or any CPU made prior to 2012. Nor will it take advantage of AVX2. I use the Intel compiler and either yield builds that I can target to specific processors or technology levels or I can add support for virtually all technologies, such that the same code will vectorize on AVX2, failing that AVX, failing that SSE3.2, failing that... you get the picture. With a suitable ARM compiler the same code would vectorize to NEON, etc.
So it's not especially interesting that Go is in this space as well. The most surprising thing about Go is that its developers seem never to have heard of any of the above languages (with the exception of Java).
I am fine with boring languages. The first language love of my life is C. It's hard to get more boring than C. If a system I build is going to be clever or sophisticated, I'm fine with that being expressed in my code, rather than as the product of the environment I happen to be working in.
C... boring? I don't think so. Not with 200+ undefined or implementation defined behaviors. Not when even something as simple as "a = b + c" can evoke nasal demons.
C is so loosely defined that it keeps you on your toes constantly. Every single line is like walking down a dark corridor with poisonous snakes... and your torch just guttered out.
There's nothing boring about C. It's pure thrill and danger. If you're not experiencing that, you're probably writing terribly non-portable, brittle code, ready to unexpectedly invoke undefined or implementation defined behavior.
Second, C is _far_ from boring. It's boring now because we all live in its shadow, but the idea that we should have high-level languages for writing low-level software in? That's C, right there.
Lots of other languages that now seem boring were interesting to start (Java, Perl, ...). What's frustrating about Go, and the hype it gets, is that it isn't interesting in any of those ways, and yet so many people, including its designers, seem to think it's revolutionary.
https://news.ycombinator.com/item?id=6498878
As you should be aware, since you mentioned Oberon.
From another angle, consider that in HPC FORTRAN codes will often get the best performance - and FORTRAN doesn't have real pointers.
This doesn't show anything new though, programming math/graphics is perfect use for C/C++ and you won't really benefit from anything Go has to offer and some of its features actually become annoyance for this kind of application. The biggest strength of of Go doesn't really really shine either as simple parallelism needed for ray tracer is matter of few lines of code in both C and C++.
There is nothing standard about the optimizations -- direct AVX use is enormously uncommon, even among extremely high performance code.
But I wouldn't say writing media and signal processing inner loops using SIMD intrinsics is uncommon. The style of optimization I illustrate is pretty common, perhaps minus the AVX code path. Most widely used video/image processing, ray tracing and other compute bound libraries will probably be SIMD optimized in some fashion (probably with different code paths for different processors). You gain 1-8x performance, which is pretty significant. It's on the same order of magnitude speedup as threading your program.
I have yet to see anyone truly and systematically trusting automatic vectorization, but perhaps there are libs out there I've missed. Anyone know of some?
I made many of the same transformations as you did: switching the object's data to a structure of arrays, splitting out the computation of the normal from the loop, etc. (even an int hit = -1.) My goal was to coax Intel's compiler into autovectorizing that loop, without directly using vector intrinsics. I succeeded, but the result turned out to be noticeably slower than just compiling kid0man's with -fast. Part of that, I suspect is that it generated suboptimal code for the reduction over the minimum, where a human programmer would have used a movemask as you did.
That said, I'm fairly curious to experiment with seeing how it would perform with the kernel compiled via ispc [1].
Regarding direct use of SIMD intrinsics in inner loops, I have to agree with you that it's still reasonably common for this type of thing. I've certainly done it before in ray tracing contexts [2], and I've seen many others do it as well, e.g. [3] and [4]. Autovectorization and things like Intel's array notation extension [5] seem to be getting better all the time, but I don't think it's generally as performant yet as direct use of intrinsics. In the cases where it is, it usually seems to have taken a fair amount of coaxing and prodding.
[2] http://www.cs.utah.edu/~aek/research/triangle.pdf
[3] https://github.com/embree/embree
[4] http://visual-computing.intel-research.net/publications/pape...
[5] http://software.intel.com/en-us/blogs/2010/09/03/simd-parall...
I actually did a version which did the reduction over minimum purely using SIMD and then a post step which reduced the SIMD minimums to a single scalar. It was somewhat tricky to get the index right, and in the end it turned out to not be faster (at least not for the little example of 32 objects, I imagine you would gain something on a more complex scene)
Anyway, it was a fun little exercise and it has sparked some interesting discussion. Thanks for posting the original.
But at that point this has nothing to do with Go or C++, and I find this whole discussion rather disingenuous (at first I thought you were detailing the maturity of C(++) compilers and their superior support of auto-vectorization, which would be a reasonable angle): You can import the Intel math libraries and call them from Go (I know, as I do it regularly. See my submissions).
I still think that a systems programming language need to offer escape hatches, whilst striving towards ease of use in the common case. C++ has plenty of hatches, at the cost of horrific complexity.
But suppose I'm willing to pay the cost of writing my code in 5 different code paths for different processors for that extra 2-4x of performance. Very few languages offer that possibility, and most of those who do only offer to call a C library. I'm the guy stuck writing the Intel math libraries of the world, and I want something more reasonable to do it in.
It would be a little nutty to suggest that Golang 1.1 is going to give optimized C code a run for its money. Nobody could seriously be suggesting that.
What is surprising is that the naive expression of an "interesting" compute-bound program in both languages are as close as they are.
Most C/C++ code --- the overwhelming majority, in fact --- is not especially performance sensitive. It often happens to have performance and memory footprint demands that exceed the capabilities of naive Python, but that fit squarely into the capabilities of naive C.
The expectation of many C programmers, myself included, is that there'd still be marked difference between Go and C for this kind of code. But it appears that there may not be.
This doesn't suggest that I'd want to try to fit Golang into a kernel module and write a driver with it, but it does further suggest that maybe I'd be a little silly to write my next "must be faster than Python" program in C.