Fast integer compression in Java
lemire.me
lemire.me
You can use it for text but it won't work as well. It's designed for numbers that are created by some kind of real process.
It compresses by looking for the largest and smallest number and only storing the difference from the smallest number, and only as many bits as needed for the largest. (Good for situations where most of your data lies inside a range.)
It stores the difference between successive numbers. (Good for measuring a process that varies slowly.)
Also see: http://en.wikipedia.org/wiki/Golomb_coding which works on the principle that small numbers show up more than large.
None of that makes sense for text which doesn't have those patterns.
Another difference is that a custom algorithm for compressing integers might gain speed/compression efficiency from exploiting statistical properties of integers e.g. it's a safe bet that in most cases the highest byte of an int is 0 (i.e. integers are are small).
Or in many cases a delta encoding is very effective (i.e. {5, 10, 15} => {5, 5, 5} which is easily encoded with run-length encoding).
Of course if integers are randomly and uniformly distributed they are just as un-compressible as random, uniform distribution of bytes, but in many real-life uses of integer arrays that is not true.
The way FOR (i'll get to PFORDELTA in a sec) codecs work is by taking numbers say 128 at a time, finding the max number of bits necessary (so if all numbers are between 0 and 32, it would be 5), and then encoding them as 5 bit numbers
FOR has the problem that if you have a list of numbers like 1 2 2 2 3 728 3 2 1, the 728 screws you.
PFORDELTA instead finds the number of bits it takes to represent, say, 90% percent of the numbers (IE it finds the b such that 90% of numbers are < 2^b), encodes those numbers in fixed size blocks of b bits, and along the way, "patches" out the 10% of outliers, and encodes them at the end.
It is normally used on posting lists.
In a sufficiently large random byte stream, for random 4 byte streams, either b == 32 all the time, or over time, the outliers will take more space than you save elsewhere. (where the amount of "more space" will be whatever the control overhead of the outlier encoding is).
So you will either save nothing, or grow in size. This is guaranteed by the pigeonhole principle.
But then again, for text there probably are much better-suited alforithms.
But you are right that their are far better algorithms for text compression (although if doing this character re-encoding makes sense, I would imagine most implementations include it along with the main algorithm, and several other bonus compression algorithms).
> Though you cannot reach the same kind of speed in Java as you can in C++, there are many good reasons to use Java instead of C++. How good is Java at this task? Direct comparisons between Java and C++ are difficult. I would estimate that the difference is a factor of 3 and more. But Java can still be more than fast enough.
That has also been my experience, but there's always an army of people that claims "Java can be just as fast or faster than C++". Anyone care to prove Mr.Lemire wrong? (My experience was more like twice as slow for memory intensive code, but thrice as slow still sounds like a reasonable estimate to me)
http://mechanical-sympathy.blogspot.co.uk/2012/07/native-cc-...
and
http://mechanical-sympathy.blogspot.co.uk/2012/10/compact-of...
It's interesting to look at what Java lacks to achieve this level of performance with less pain. The main issue, I believe, is control over memory layout. For example, avoiding boxing in arrays of objects. Avoiding GC is also an issue.
I think Rust is interesting as it allows the programmer to talk about these things without going "outside" the language like one must in Java, while still retaining a modern programming style.
Because I can get so close to native performance in that highly specific case, the idea of using C++ hardly ever comes up. If I need faster than this, C++ is probably not fast enough either and I need to look at "closer to iron" solutions. I for one appreciate that. But then again, I'd much rather program in C than C++.
As far as claims like Java is 3x slower than C++, it is nearly impossible to quantify these things directly. For any given benchmark you will be able to find people who complain about the code in the benchmark not being optimized enough.
Suffice it to say that there are some things Java is faster at and some C++ is and their are techniques in both that allow them to achieve near parity.
The blog posts I linked compare performance to nio ByteBuffers, and show improvement for Unsafe over ByteBuffer. That might be relevant to your case.
1. Have added an extra step to your build process
2. Pay the cost of doing JNI calls, which I believe is quite high.
Look, I'm not saying doing this Unsafe stuff is rainbows and unicorns. It's a hack, but it's a hack that is extremely useful in a few niche applications.
Another common option (especially in server style programs) is to have 2 daemons, one in C (or C++) doing a very specialized role and communicating with a JVM daemon via memory mapped files.
Proving my claim is very hard, but I'll try to persuade you. The task is made harder by the fact that for every possible Java program, there exists a C++ program that performs the exact same task at least as fast as Java (proof by example: the JVM).
And yet, Java is the best performing programming environment in existence (well, in widespread use; there may be runtimes I'm not aware of). How is that possible? When we say that one language or environment is faster than another, do we mean that there exists a string of characters in that language that would produce a faster executable? If that is what we mean, then yes, C++ is faster than Java. But I think the question is not whether that desired program exists, but whether you can write it. To that question, I think the answer is no in most circumstances, especially when it comes to large programs.
Why? because when you write a large, hopefully maintainable, C++ program, you must make use of language features that are hard for the compiler to optimize, most notably -- virtual methods. The JVM JIT does runtime compilation, so it inlines virtual calls, and de-optimizes them if its assumptions are shown to be false. Method inlining is the most important compiler optimization because it opens to door to all kinds of other optimizations like lock elision, scalarization of objects and more.
And there's another issue. What do we mean by performance nowadays? It's not too hard for a single-threaded C++ program to beat a similar Java program (provided that the program is not large, and does not make use of virtual methods). But modern hardware is multi- and many-core, and most scalable lock-free algorithms absolutely require a good GC in order to function well. Java probably has (several of) the most advanced GC(s) in wide use.
To sum up: C++ can beat Java in small benchmarks, but Java will beat C/C++ in most large, real-world programs; and if the program is heavily multi-threaded, Java has even more of an edge.
Note that there are various other tricks (eg. hazard pointers, reference counters, etc.) to get around the same problem but they're mostly specialized to various situations. As an example, last time I checked gcc's std::shared_ptr implementation on x86, it used some lock-free magic to implement its thread-safe reference counter. In fact, I think it could even be used to build entire lock-free data-structures and never have to worry about deleting anything.
First, various techniques for avoiding GC in lock-free data structures are not in wide use and for good reason: they are far more complicated to implement in a general fashion than you think. Sometimes they're very specific, sometimes they require cooperation by the kernel, and sometimes you end up writing a buggy and inefficient garbage collector just to get around implementation problems. Java's lock-free data structures are used everywhere today and with great results.
Second, regarding real-time. There are hard real-time GCs for Java, and they work great, only they tend to cost a lot of money. But hard real time systems often sacrifice performance for the sake of worst-case guarantees -- that's a whole other set of tradeoffs. In general, and certainly when it comes to throughput, a good GC is both the easiest and the most efficient way to manage memory.
Finally, because, as I've shown, for every Java program there exists a C++ program that performs as efficiently or better, you could, given enough effort, always surpass Java. In practice, doing that is extremely hard (unless it's a very specific, well understood problem), and the returns are hardly worth the effort. What you gain in integer compression you lose in method invocation. What you gain with SIMD you lose on malloc. What you gain in cache-friendliness you lose in multi-threading. And the gap is only narrowing in Java's favor so that there are fewer and fewer things you gain when using C++.
This is not to say that C++ doesn't have it's place. First, you need it to write the JVM :) You need it in a resource constrained environment where memory is limited, and you may not want to spare a thread for concurrent GC. You need it when startup time is crucial. You need it (or Fortran) for some well-understood numeric computing problems. But for general, large, long-running server side applications - Java is king.
And BTW, games are years, if not a decade or more, behind advances in software engineering (I've had some experience in that). In the past decade they've literally progressed on one front only (though, an important one): graphics and GPGPU (for physics and AI). I know that some AAA studios literally forbid any multithreaded code on the CPU (beyond a simple and predefined division of labor among a few threads). MMO game engines, for examples, are practically primitive compared to advanced server-side systems in other markets.
The biggest problem holding back RCU is not how hard it is to use (if you're qualified to write lock-free data-structures then it should be cake) but that it's relatively unknown and there are not many user-land libraries. I know of 4 available user-land implementations: two that I've written, one that we use at work and only the fourth, liburcu, would I consider mature and stable enough for wide-use. There's a third reason but I'll let you figure that one out by reading the Wikipedia article.
Note that it's not a magic bullet either because there are various possible implementations and they offer various trade-offs. A bit like the various GC mechanisms really. Funny thing is that I usually introduce RCU as a GC mechanism; albeit one that's far more specialized and offers many advantages over a traditional GC when it comes to lock-free data-structures.
Final note, I've written various mechanism for cleaning up lock-free data-structures and I'm fully aware of how difficult they can be to implement correctly. If nothing else, it's more complicated then using a GC where there's nothing for you to do. The point of my previous post was not to say that one approach or language is better then another but merely to point out that a full-blown GC is not required to build a scalable lock-free data-structure. Good thing for me since that's exactly what I do for fun :)
Another point is that Java bytecode doesn't have SIMD primitives, so unless JIT can do SIMD-optimizations, any SIMD-aware C/C++ compiler will beat Java.
This is especially relevant for the kind of task this article discusses: PFOR Delta integer compression.
Actually, there are other changes that would improve Java performance more than SIMD, like value types. The lack of value types causes many unnecessary cache faults in Java programs.
That goes counter to my experience. Care to share your anecdata here?
Like I said, this happens for two reasons: large maintainable programs require virtual methods that are hard for the C++ compiler to optimize, and Java opens a whole new realm of possibilities with scalable non-blocking data structures.
I would say this is true for most large applications, but untrue in particular circumstances: Java does not excel in many cases of numeric computation (where value types, and, yes, SIMD, would be of assistance). Also, Java is the wrong choice if startup time is important.
Which, incidentally, is my tool of choice...
Personally, I've never met nice readable Java code. I'm sure it exists somewhere, but I've actually gone looking for it in the past (including posting a question on HN, out of 10 responses only one was concrete -- Play Framework -- and they've abandoned Java since).
Every piece of Java I've met is an overengineered piece of AbstractAbstractFactoryFactoryBeanSingleton class. I'm sure Notch writes better code. But as far as I can tell he's the only one.
Not at all. Python does not even begin to apply a minuscule portion of the optimizations the JVM performs. Also, I wasn't talking theory, I was talking practice: I've worked on porting two multi-million LOC soft real-time applications from C++ to Java, and a few smaller programs; big performance improvements in all cases. I was simply explaining why Java performs better in practice, not speculating why it would. But if you haven't seen that with your own eyes, all that's left for me is to explain why this is so.
> Personally, I've never met nice readable Java code.
This has no relevance to the discussion, though there's plenty of nice, readable Java code. Anyway, I was talking about the JVM, not specifically Java the language.
> I'm sure Notch writes better code. But as far as I can tell he's the only one.
Right... and as far as I can tell, you're not really into Java :) That's okay. Millions of developers are.
Are you familiar with PyPy? It doesn't go as far as the JVM, but "minuscule" is definitely wrong.
> I was talking practice: I've worked on porting two multi-million LOC soft real-time applications from C++ to Java, and a few smaller programs; big performance improvements in all cases.
My experience is the opposite. Not two million lines - just a few tens of thousands, but still.
> This has no relevance to the discussion, though there's plenty of nice, readable Java code. Anyway, I was talking about the JVM, not specifically Java the language.
One of the previous posts (to which I was replying) claimed that Java is faster in practice because (paraphrasing) it is nicer to read and write, and that was a response to that.
> Right... and as far as I can tell, you're not really into Java :) That's okay. Millions of developers are.
That's good, that guarantees I'll always be in demand :)
But that's the whole point. I could probably write a C++ program of that size that beats a Java program of that size, provided that they're single threaded. But programs of that size are usually written by a single developer or a very small team. Many programs aren't that small.
But even at this size, unless the problem is embarrassingly parallel, a multi-threaded Java program would often perform better, mostly due to concurrent data structures that require (or greatly benefit from) GC.
I'm no fan of Java, but I am a fan of the JVM. The two don't necessarily go together, as I'm sure you know. I also recognise the JVM has limitations. There will (hopefully) be better things in the future. This is one reason that Rust is interesting to me, but it has a long way to go before I'd use it in production.
The point was, a badly written Java program can beat a badly written C++ program, because the Java optimiser is more robust against stupid decisions by the programmer.
PyPy might make your point correct though. While PyPy will never beat well-written C++ (at least, not in the foreseeable future), it might start beating badly written C++.
I see main Java advantage in concurrency and advanced programming. My code has fine-grained locking, robust, correct and very quickly written. And hand optimized code in bottlenecks usually has comparable speed.
They are not necessarily in contradiction. Java can be as fast or faster than C++ - if you choose a workload whose active set (including Java overheads) fits into the CPU cache. Of course there are very few realistic and useful workloads where that is the case.
Still, most things don't have code execution as a bottleneck and/or aren't so performance critical that a factor of 3 is a big problem.
I worked with half floats (FP16) and other weird partial word float formats ... I don't think it's trivial.
I guess the most straightforward way is to convert to fixed point real numbers first, then it's essentially the same as integers.
Or maybe just do it per block of each 128 floats?