Hacker News new | past | comments | ask | show | jobs | submit | NotRustAgain's comments login

https://benchmarksgame.alioth.debian.org/u64q/java.html

Consistently twice as slow, with memory usage between 2 and 400(!) times larger. Java is slow and bloated compared to native.

Your next argument will be something about how micro-benchmarks don't reflect reality for larger programs...


> Java is slow and bloated compared to native.

What do you mean by "native"? C/C++? Yes, given enough effort you can write C code that outperforms Java code, sometimes handily, especially in single threaded or low-contention cases. Yet we did write applications in C/C++ before we switched to Java, we had excellent reasons to make the switch, and there have been precious few companies making the switch back. That shows you that the microbenchmarks don't tell the whole story.

Right now, the JVM's biggest performance issue is the lack of value types. Once they arrive -- and work is well underway -- beating Java would be harder and harder, especially given new compilers like Graal. But in any event, I just don't see large developers switch back to C++ (or Rust) en masse. Memory usage is hardly an issue, as RAM is very cheap and underutilized in server environments anyway. Spending effort to conserve RAM just doesn't make any sense, unless you're targeting RAM constrained environments.


I feel with companies moving to cloud environment defending Java memory bloat will be harder and harder. Java's limited value types may be out by 2020-21 or so and it will be many more years libraries ecosystem start utilizing it. So we are still at least 10 years away from some form of Java value type available for general users.


1. Memory "bloat" is always a better use of resources than increased development time, regardless of how you have to pay for it.

2. How are Java's value types limited?

3. I find your calculation extremely pessimistic. Lambda expression are widespread a couple of years after their release, and I see no reason why value types will be different. I think that 5 years are a better estimate for wide use of value types.


Here is what I read from JEP 169

> Except for pointer equality checks, forbidden operations will throw some sort of exception. How to control pointer equality checks is an open question with several possible answers.

And more:

  Rules for permanently locked objects:
  
  - restrictions on classes of locked objects
      - all non-static fields must be final
      - there must be no finalizer method (no override to `Object.finalize`)
      - these restrictions apply to any superclasses as well
      - an array can be marked locked, but then (of course) its elements cannot be stored to
      - if not an array, the object's class must implement the marker type `PermanentlyLockable` (is this a good idea?)
  - restricted operations on locked objects (could be enforced, or else documented as producing undefined results)
      - do not use any astore or putfield instructions, nor their reflective equivalents, to change any field
      - do not lock (you may get a hang or a LockedObjectException)
      - do not test for pointer equality; use Object.equals instead (there may be a test for this)
      - do not ask for an identity hash code; use Object.hashCode instead (there may be a test for this)
      - do not call wait, notify, or notifyAll methods in Object
      - at the time it is marked locked, an object's monitor must not be locked (in fact, should never have been?)
  - side effects
      - elements of locked arrays are stably available to readers just like final object fields (i.e., there is a memory fence)
      - a locked object can be locked again, with no additional effect
      - any attempt to mutate a permanently locked object raises java.lang.LockedObjectException
      - any attempt to synchronize on a permanently locked object raises java.lang.LockedObjectException
  - object lifecycle
      - all objects are initially created in a normal (unlocked) state
      - an object marked locked cannot be "unlocked" (reverted to a normal state)
      - an object marked locked must be unreferenced by any other thread (can we enforce this?)
      - the reference returned from the (unsafe) marking primitive must be used for all future accesses
      - any previous references (including the one passed to the marking primitive) must be unused
      - in practice, this means you must mark an object locked immediately after constructing it
  - API
      - the method `lockPermanently` is used to lock an object permanently
      - there is a predicate `isLockedPermanently` which can test whether an object is locked or not
      - for initial experiments, these methods are in `sun.misc.Unsafe`; perhaps they belong on `Object` (cf. `clone`)`
With all this above I feel it is not exactly same as understood in languages which natively support value type.


Java will natively support value types (and already does, just not user-defined value types). What you've quoted is the spec for locked arrays; those arrays are not value types, but reference types. I'm not sure about the relationship between the text in that JEP and the current work on value types: http://openjdk.java.net/projects/valhalla/


It's interesting that the "gz" column, which reflects the amount of source code required, shows a very slight trend towards C being smaller, although there are big exceptions like regex-dna and reverse-complement.

I'm not seeing the 400x more memory - there is a 40x though. But if you add up the columns for the programs that have both C and Java versions, you get this rough summary...

           secs    KB       gz
    Java   76.33   2004048  12775
    C gcc  37.32   793652   11651
...that Java is on average half as fast as C and uses 2.5x more memory, while requiring slightly more source code.



"half as fast as C and 2.5x the memory on average" is a stupendously good result for any managed runtime. Most don't get anywhere near it while providing more than acceptable performance in practice.


Yes, and 2.5x is something that can be dealt with by deploying beefier hardware. The productivity gain is what makes that tradeoff a no-brainer.


I mean, micro-benchmarks are just generally not something to put a huge amount of stock into in general, regardless of language. Software that scales out to hundreds of thousands if not millions of users is generally written in "slow", "bloated" languages, and seems to be doing ok. It really all depends on the use case, and who is creating the software. Most enterprise software is slow and bloated, but a lot of other stuff exists that is quite the contrary. Two easy examples:

- Cyberduck is written in Java, yet nobody seems to be complaining about how slow and bloated it is.

- There exists a version of Quake 2 written in Java: http://bytonic.de/html/benchmarks.html It seems to be able to push out more frames than native, actually.

Both of those are real software that actually run well on Java, regardless of penalty paid for running in a VM, JIT, and GC.

Also when looking at the amount of memory and time taken when comparing C to Java in the microbenchmarks, always be sure to mentally factor in the base overhead of starting up the JVM both in time and memory. It's pretty easy to see that when a C version takes up something like 100KB of memory and the Java one takes up 30MB, Java consistently takes up at least ~30MB regardless of how much memory is required for that microbenchmark. When the C version takes up 300MB and the Java one takes up 700MB, that's a bit better of a comparison. (though still not perfect, because the Java GC will reserve a lot of memory for itself, even if it isn't using the full 700MB, if it feels it needs that much, etc.)


> Cyberduck is written in Java, yet nobody seems to be complaining about how slow and bloated it is.

Lol I just double checked that after you said it, it never felt "Javaish" even the interface is really sane.

Actually they are using JNA for a lot of stuff and they written foundation bindings... (https://g.iterate.ch/projects/ITERATE/repos/cyberduck/browse...) cool stuff I keep that for reference. However he should put the bindings stuff under something like LGPL since most stuff will fall under fair use anyway (simple class Names which you would use anyway even without looking at the Source when making a JNA binding to Cocoa)


>> overhead of starting up the JVM both in time

Not much for those programs:

http://benchmarksgame.alioth.debian.org/sometimes-people-jus...

>> It's pretty easy to see … memory

Yes: http://programmers.stackexchange.com/a/189552/4334


It seems to be able to push out more frames than native, actually.

The table in your link shows it being 6% faster in one (literally) corner case, while all the other entries show it being slower by varying amounts. Keep in mind that in this benchmark a lot of the "heavy lifting" is being done by the GPU via OpenGL, so it isn't great for benchmarking languages that run on the CPU. It also doesn't mention what the "Original C Code" was compiled with.


Does their exist a JVM with an OS level shared heap, to reduce GC overhead? Obviously each can be collected independently of one another so pausing shouldn't be a significant issue and IPC becomes a Normal heap reference which may aid in performance.



Ah, synthetic benchmarks. Are you really sure they're consistently twice as slow? Your link shows otherwise.

  mandelbrot 17% slower
  fannkuch-redux 34% slower
  fasta 38% slower
  fasta-redux 39% slower
  pidigits 44% slower
So much for predicting my next argument.


How come you left out these ones?

    spectral-norm 115% slower
    reverse-compliment 121% slower
    n-body 136% slower
    regex-dna 235% slower
    k-nucleotide 253% slower
Cherry picking your data shows you're either dishonest or really lost in your denial.


Perhaps you should read his reply more carefully next time. He said the benchmarks were "consistently 2x" as slow. He was wrong and the rest of the synthetic benchmarks you posted just corroborated my point.

As for being dishonest, well, you're commenting from a newly minted temp account so it would seem you didn't have the courage/backbone to use your real one.


> Almost no libs aren't avail on 3 now.

What an awkward way to say "most libs work on Python 3 now"... Why is that? I suspect it's because saying it clearly doesn't deliver the message you want.


> but the library it ships with is going to privilege one over the other

This really just seems to me a matter of no one having done it right yet...

Following your example (which I like), I can have both mutable and immutable types in my language. Your point is that the library will favor one or the other, but I think you admit both libraries are possible. Why can't I have twice the library?

Yeah, it might be more work to have a library twice as big, (and again, no one seems to have done it yet) but it's not more work than having two languages with two libraries.

All that to say, you're summarizing what's been done, but you haven't proven that something better couldn't be done.


> a passenger vehicle, dump truck, submarine, and airplane

The problem with using metaphors to make your argument is we generally have to argue about whether the metaphor is even appropriate enough that the conclusions apply to the original topic... It's simpler just to argue the topic.

Within a huge class of problems, I don't need to get a new computer to solve each new thing that comes up. That's a very general tool. Why do I need different programming languages?

> All-in-one compromise solutions only excel [...]

Who asked for a compromise? I could make a short list of all the features I want in a language, and while there isn't one single languages that currently has all those features, I doubt you could make a proof that creating such a language is impossible or would involve some horrible trade off. Your list might be different than mine, but that's not the point.


  > The problem with using metaphors
Then ignore the metaphor and focus on the longer paragraph that succeeds it. :P

  > I don't need to get a new computer to solve each new 
  > thing that comes up
Except that, in practice, you do. I have a smartphone in my pocket, a laptop in my bag, a desktop in my office, and two personal servers in the cloud. Just because two computers are both effectively Turing machines does not automatically invalidate the importance of form factors, power draw, integrated peripherals, physical location, and other practical differences. This also ignores the existence of domains that actually demand dedicated hardware, like supercomputing. We are never going to live in a world where microcontrollers are just as capable at running weather simulations as the TOP500, because the economics don't pan out. So no, you're right, it is not logically impossible to construct a language that is capable of performing all imaginable tasks, though that's not something that I've ever disputed. Rather than being logically impossible, it's merely economically infeasible. :P


> Then ignore the metaphor and focus on the longer paragraph that succeeds it. :P

Your second paragraph had a bunch of economic pseudo-theory about what sells... Maybe that explains why we don't have a good general purpose programming language, but it said little about whether there could be one.


Economics is a social science, pseudo-theory is the bulk of it. :P However, I welcome you to prove me wrong by creating the language to end all others.


> :P However, I welcome you to prove me wrong by creating the language to end all others.

Yes yes, I'll be sure to let you know. In the meantime, I hope you'll keep up the great work maintaining the status quo and contributing to a language which avoids problems that experienced programmers don't really have.

:P


>> A single language needed to solve all problems is a fallacy.

> Has that been proven and do we have pointers to any peer reviewed papers on that?

I agree with you and can't stand it when people say things like: "All languages have their strengths and weeknesses" or "you just need to choose the right tool for the job".

I can't see any reason a clean and simple language can't have high level constructs when you want them and low level performance when you need it. It's just that no one has done it well enough yet.


If you drill down far enough it's all binary or byte code. But on the way back up, there are variations of implementation in more than a single language.

Pseudo inception like self describing languages are close but never 100% self describing (this always blows my mind). Then there are languages implemented on top of C, and the many XVM languages (elixir built on the erlang virtual machine, JVM langs, etc)

Any particular language is almost always composed of multiple languages, so trying to craft one to solve all problems is an interesting problem.


There are several things you could take away from Lisp. I don't think you caught the good parts yet though.

My idiot instructor for comparative programming languages set me back years in understanding the cool parts of Lisp. He focused on "List Processing", which seemed pointless to me because lots of languages have lists... (I work with him now, so I'm allowed to call him an idiot)

You seem to think it's about the function calls. Maybe you had a bad instructor too, and he focused on "functional programming", which is all the rage for the last decade. I dunno, maybe you came to that conclusion on your own.

The thing I now think is wonderful about Lisp (Scheme for me), is that you can write your program however you want. If you want to use switch statements, and your language doesn't have one (Python), just make your own:

    (switch foo
        (case bar -> (do whatever you want))
        (case hmm -> (do something else)))
Some languages have backtracking:

    (backtrack
        (keep trying)
        (different things)
        (until it works))
You just can't add those kinds of features to most languages because most languages are not programmable...


Fast compile times are cool, but I've never heard anyone say they liked programming in a language that Wirth created. The possible exception is Delphi as a Pascal derivative, but that has very little to do with wanting to program in Pascal.


Pascal was the language of choice for Windows development for quite some time even before Delphi.

Also, for what it's worth, Ada has quite a few Wirthisms and I personally quite enjoy it. But it technically isn't a Wirth language.


Pretty much every 20 year old codebase is a mix of terrible stuff. The exceptions are rare and usually involve a strong handed dictator who is willing to make cleanups from time to time.

You're lucky it isn't Fortran and a homegrown (crappy) macro language.... You can't fairly judge Scheme or C from a legacy codebase unless you judge every other language that way too.


I'm judging only from its debugging capabilities - and it looks like even now there's no (open to all) way to do that. Compared to that, C even if that's 20 years old code too, has some great debugging tools for it.


I really wanted to use Rust for numerical/scientific computing tasks, but it's kind of miserable for it. I didn't get hung up on the ownership things that all of the Rust zealots talk about (although I think explicit lifetimes are needlessly complicated). I got hung up trying to implement simple things like complex numbers and matrices in a way that was generic and usable. I'm sure some Rust fanboy will argue that Rust has operator overloading through traits, so I'll challenge anyone to make a workable implementation such that zA and 2.0B works in the following generics:

    let z = Complex<f64>::new(1.0, 2.0);
    let A = Matrix<f64>::ident(10, 10);
    let B : Matrix<Complex<f64>> = z*A;
    let C : Matrix<Complex<f64>> = 2.0*B;
If Rust can't do scalar multiplication or real to complex conversions, it's really not usable for what Eigen or Numpy can do. Try defining the Mul trait generically for those multiplication operators, and you'll see what I mean.

(yes, I know there are some syntax errors in the type declarations above - It's my opinion Rust got that wrong too...)

Supposedly this kind of thing will eventually be possible with the recent "specialization" changes, but I haven't seen anything that allows operators to work as above...

ps: Last I looked, there was fledgling support for SIMD on the horizon... LLVM supports that, so it could happen.


This is essentially a complaint that Rust went with strongly-typed generics like every statically-typed, non-C++, non-D language instead of untyped templates like C++ and D. I think that the difficulty of reasoning about template expansion type errors, the complexity of SFINAE, and the consequences for name resolution like ADL make templates not worth the added expressive power for Rust, especially when the features needed to support your use case can be added in the strongly-typed setting eventually.


I don't think that's quite right... ISTM that it's more about lack of multi-parameter type classes/traits[1] (+ specialization, I guess) and the fact that it went with the traditional non-symmetrical dot-based notation for dispatch... which means that the first "trait-parameter" would always be "privileged" (at least syntactically).

[1] At least I don't think it has those yet, right?


Rust has had multi-parameter traits for a very long time - possibly they were never not multi-parameter. All of the binary ops are multi-parameter. The first parameter is just an implicit Self parameter, which does preference it syntactically, but semantically it is not privileged in any way.

The issue that operator overloading math crates come into is that Rust's polymorphism is not only type safe but also makes stronger coherence guarantees than systems like Haskell do. You can't implement all of the things you want because Rust guarantees that adding code to your system is a "monotonic increase in information" - adding an impl is never a breaking change to your public API, and adding a dependency never breaks your build. Haskell does not make these guarantees.

There's no way to "solve this" entirely because there are a bunch of desirable properties for type class systems and they fundamentally conflict, but I think with specialization and mutual exclusion (the latter hasn't been accepted yet) Rust's system will be as expressive as anyone needs it to be while still maintaining coherence.

Of course in this context Wadler's law should be taken into account, and the grandparent poster could maybe revise their strength of opinion about syntactic sugar and recognize that this is about solving a much more complex problem than how to make matrix multiplication look nice. https://wiki.haskell.org/Wadler's_Law


I stand very much corrected. I must admit I wasn't sure and just skimmed a little documentation and didn't see any examples of MPTCs.

> The first parameter is just an implicit Self parameter, which does preference it syntactically, but semantically it is not privileged in any way.

Yes, but I think the syntax was actually what bothered the OP who was complaining about linear algebra. At least C++ has free functions for operators. (I assume, again without knowing, that Rust doesn't, otherwise OPs "demands" should be easy to meet, given specialization.)

I mean any kind of "double * matrix" or "vector * matrix" or ... should be easy to support if operators are free functions and there's MPTC and specialization. EDIT: Actually, come to think of it, for this situation (algebra) you don't really need specialization, you just need overloading. (Since none of the types involved are sub-types of each other. It could be argued that a 1xN matrix ~ N-vector, but that's probably not worth supporting.)

Generally, I just think it's a mistake to even support the magic "dot" notation and thus privileging one operand over any other, but I guess we're getting off topic.

Thanks for the lesson, btw! :)


Again, the issue we run into is with coherence. The Rust team decided that adding a dependency should never in itself break your build, and adding an impl/instance to a library should never be a breaking change. Haskell doesn't make this guarantee. C++ doesn't even typecheck parameters.

Providing this guarantee means establishing what are called orphan rules: you can only `impl Trait for (Type1, Type2, ...)` if a certain portion of those traits and types are defined within the same library as the impl (the exact rules are nuanced, there's an RFC that defines them). The rules that were arrived at as providing the best guarantees unfortunately make it difficult to implement the operator overloading traits in the way a lot of numeric libraries want.

For example, you can't define a trait `Integer` and `impl<T: Integer> Add<T> for T`.

I'm actually not sure what the OP's specific complaint is, but its ultimate cause is something along these lines.


> This is essentially a complaint that Rust went with strongly-typed generics

It could also be a complaint about how operators are implemented, e.g. in Scala they're just methods (no need for a special trait). That's not to say I don't think Rust made the right choice, but Scala went with strongly-typed generics, and can allow implementing the asymmetric multiplication operators.


If operators are just specially named methods, then with strongly-typed generics you can't write generic functions that work on anything that (for example) implements the + operator, because traits (concepts) have to have a specific signature.


Yes, you're right. The only way I can think of to handle it (in Scala, I suppose you can't do it in Rust) would be via implicit parameters. You would basically have a Times trait, and a TimesOp trait.

    trait TimesOp[L,R,O] {
        def times(L left, R right): O
    }

    object TimesOps {
        implicit object ScalarMatrixTimesOp[S] extends TimesOp[S,Matrix[S],Matrix[S]] {
            override def times(S left, Matrix[S] right): Matrix[S] = {
                ...
            }
        }
    }

    trait Times { self: L =>
        def *[R,O](R right, implicit timesOp: TimesOp[L,R,O]): O =
            timesOp.times(this, right)
    }
I'm sure I got something wrong here, but I think the basic idea works. In any case, it goes to show that getting this behavior in a language with strongly-typed generics is non-trivial.


No. The complaint is that Rust is not good at expressing numeric algorithms. The poster above asked about a Rust library like Eigen (C++) or Numpy (Python)... It doesn't matter - I suspect you would dismiss any criticism regardless.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: