30 ms·
No complaints about the meat of the article, just with two introductory remarks: > For example, Ruby famously values Developer Happiness and that value has imp
by catnaroek 11y ago
No complaints about the meat of the article, just with two introductory remarks:
> For example, Ruby famously values Developer Happiness and that value has impacted Ruby’s features.
Really? Every time I give Ruby another chance (admittedly, not too often), I end up feeling angry. I can stare at five lines of code for several minutes, and have no idea what it will do. Not even Haskell.
> Ruby also protects you from segmentation fault errors. But to do so it uses a garbage collector. This is great, but it has a big impact on your program’s speed.
Is garbage collection the main culprit? Not duck typing? Not dynamic metaprogramming? I reckon performing a hashtable lookup during every single method call has a bigger effect on performance than garbage collection.
- pcwalton 11y ago> I reckon performing a hashtable lookup during every single method call has a bigger effect on performance than garbage collection. Well, a high-performance Ruby VM will perform inline caching to mitigate this. And if the VM isn't a high-performance VM (i.e. it's interpreted), then the overhead of the interpreter dispatch loop dominates everything.
- catnaroek 11y agoI'm admittedly not very familiar with compiler backends - especially not JIT compilers. But how does this optimization even work if I'm constantly modifying the method tables of objects at runtime? If I understand correctly, ORMs and Web frameworks for dynamic languages do this sort of thing all the time.
- cwzwarich 11y agoIn a JIT for a dynamic language, you generally have to predicate your more aggressive optimizations on some runtime condition, e.g. "this variable is actually an integer", or "this object has the same method layout as other objects did before". These conditions are checked before relying on the optimization, and if they fail, the generated code jumps into a runtime that may decide to use an interpreter, generate deoptimized code, etc.
- catnaroek 11y agoThis actually sounds more complicated thank thinking ahead how to define your abstraction in more precise terms. (So that you don't need to perform optimistic optimizations that you might later need to roll back.)
- deleted 11y ago[deleted]
- icebraining 11y agoI don't understand, who should define such abstractions? If you're the JIT developer and you define a value encoding that supports all possible values (so that the JITed function never has to be rolled back), then you can't really optimize. For example, if the JIT has the code "function(a, b) { return a+b; }", and it sees that the function has been called 50 times with a and b as integers, it can generate a machine code implementation that is just a couple of instructions adding two registers. If you keep the abstractions wide enough to support other values, you can never achieve this efficiency.
- catnaroek 11y ago> I don't understand, who should define such abstractions? The language designer. > If you're the JIT developer and you define a value encoding that supports all possible values (so that the JITed function never has to be rolled back), then you can't really optimize. Obviously, the solution is to know the types of values ahead of time. This is precisely where a language with more precise abstractions can make the lives of implementors easier.
- icebraining 11y agoWell, yes, but the whole point is to make the lives of the end programmers easier, not of the language implementors. If you want to avoid forcing people to manually tag each variable, the implementors either need to write an optimistic JIT or a really good type inference engine.
- crzwdjk 11y agoThey're generally operating on the assumption that there's some amount of setup that you do initially, maybe the first time you try to call methods on your magic ORM object, that will set up the inheritance for that class and inject the appropriate methods, and then this will mostly stay static for the remainder of the process lifetime. This seems to me like a generally reasonable assumption.
- catnaroek 11y agoI don't think this scales to systems with more than, say, 50 tables. In an ERP system with dozens of thousands of tables and views, and which supports adding new functionality to a running application server, you can't assume that the database schema will remain fixed throughout the lifetime of the application server process.
- cwzwarich 11y agoThe notion that the overhead of interpreter dispatch dominates everything else (and thus the most focus should be placed on optimizing dispatch) was formed with experiments against microarchitectures that are fairly primitive by current standards. Earlier indirect branch predictors didn't take much execution history into account, mostly associating a predicted branch target with the address of a branch instruction. With a predictor of this kind, using a distinct branch instruction for distinct execution paths dramatically improves the amount of context available to the branch predictor. The same remains true if you add a small amount of context to the predictor. Over time, conditional branch predictors incorporated more branch history, e.g. the various designs like OGEHL / TAGE based on associating a branch instruction with multiple branch histories whose lengths form a geometric series. The same trick was quite successful when applied to indirect branches, although it is more difficult to implement because the result of a prediction is an address rather than a single bit indicating the branch direction. You can see evaluations of various benchmarks against both actual Intel processors and simulations of the leading academic branch predictor designs here: https://hal.inria.fr/hal-01100647/document https://hal.inria.fr/hal-01100647/document On Haswell and later (and with their model of ITTAGE) the mispredictions encountered by a switch-based interpreter loop are negligible. That being said, I would still write an interpreter in assembly myself, because the code generators found in traditional compilers do a poor job of generating code for interpreters, and you can do a better job of deciding what work goes on the inline fast path and what goes on a slow path. When you're writing an interpreter by hand, you might as well use separate indirect branches for dispatch because there's no impedance mismatch like there is in C/C++.
- bogomipz 11y ago"because the code generators found in traditional compilers do a poor job of generating code for interpreters," Are you referring to the assembler in the GNU toolchaine or? Could you elaborate on this sentiment? Thanks.
- cwzwarich 11y agoI mean "code generator" as in the backend of a compiler that comes after most optimizations have been done. In particular, most common register allocators and schedulers are designed to optimize code that looks much different than an interpreter loop, and they generally do a poor job on interpreters.
- icebraining 11y agoReally? Every time I give Ruby another chance (admittedly, not too often), I end up feeling angry. I can stare at five lines of code for several minutes, and have no idea what it will do. Not even Haskell. I mostly feel the same, but the reality is that Ruby is used by plenty of programmers when they have the choice, even if they use something else at work. Is garbage collection the main culprit? The author didn't say it was.
- bjz_ 11y ago> Every time I give Ruby another chance (admittedly, not too often), I end up feeling angry. I use Ruby every day at work. I end most days feeling angry. You never have any real idea what is going on due to the layers and layers of metaprogramming. Nil-pointer errors abound, duck typing is idiomatic, inheritance is a thing, and the minefield of mutability is everywhere. Concurrency is nigh impossible. Ruby looks pretty and simple at first glance, but the deeper and deeper you look, the more you realize how rotten it is at the core. :( /rant
- steveklabnik 11y agoMatz has said that the overall guiding principle of Ruby is "developer happiness." While some people can disagree with this, it's a pretty standard things Rubyists say, because Ruby does make many choices that prioritize their happiness over other things.
- catnaroek 11y ago> It's a pretty standard things Rubyists say, Ruby does make many choices that prioritize their happiness over other things. [emphasis mine] Isn't this true of most languages with a sizable user base? Most language designers seek to “please their constituency” to some extent. Do you know any language with a feature that was put into it in spite of opposition from the majority of its users?
- steveklabnik 11y agoI mean, no language wants to make their users unhappy, but it's about the priorities of the language designers. So for example, if you ask Rust's core team what we focus on, we say: 1. safety 2. speed 3. concurrency If you ask Matz about Ruby, he says > I hope to see Ruby help every programmer in the world to be productive, > and to enjoy programming, and to be happy. That is the primary > purpose of Ruby language. https://www.youtube.com/watch?v=oEkJvvGEtB4 https://www.youtube.com/watch?v=oEkJvvGEtB4 As a good example of this, a lot of programmers are much happier with a GC than with manual memory management, or a Rust-like system. But Rust's core values mean that it can't have a GC, even though that might make some people happy.
- catnaroek 11y ago> a lot of programmers are much happier with a GC than with manual memory management I don't disagree. I'm happier when I don't have to worry about irrelevant things, like manual memory management. And of course I'm happier when I can achieve more results with less effort, like when there is a libary or framework that does what I need. This is only natural. But not everything about Ruby is a happiness or productivity booster: (0) Ruby doesn't lend itself to writing code that documents its own structure very well, which is a time sink when the code changes faster than your ability to document the changes. [No, tests aren't documentation. At best, they only document your intentions, not the actual code.] (1) Ruby makes it unnecessarily difficult to perform basic sanity checks, like ensuring that you haven't forgotten any `when` branches in a `case` statement. The smallest testable unit is a method. Try unit-testing a loop invariant. (2) As if the above technical deficiency weren't enough, culturally, Ruby pushes towards more cumbersome forms of testing [behavior-driven development], rather than less [property-based testing]. Note that none of these disadvantages has anything to do with the fact Ruby uses a garbage collector.