Apple Open-Sources its Compression Algorithm LZFSE
infoq.com
infoq.com
The InfoQ post mentions xcodebuild, but there is also a Makefile. I really appreciate the presence of a no-nonsense Makefile. No autoconf, no pkgconfig, just plain and simple make. Also, because nobody mentioned it: yes, it compiles on Linux out of the box.
I remember reading one paper whose conclusion was running SIMD instructions can be bad for power consumption: while you need the processor in a higher power state for longer without, you can keep the SIMD unit powered off.
[Edit: That said, I have no idea how true this is for the modern Intel CPUs yet alone Apple's custom SoCs.]
Indeed, the current version of their Makefile is a great example of how to write a simple yet portable Makefile:
https://github.com/lzfse/lzfse/blob/33629bc65f4b356072c9a7d5...
> No autoconf, no pkgconfig, just plain and simple make.
While I agree with your sentiment, I believe that your statement about pkg-config goes a bit over the top.
Yes, the LZFSE project doesn't use pkg-config, but it also doesn't have any library dependencies. There's not a single "-l" argument in the linker flags.
If it had, I would prefer pkg-config over any other mechanism, as that is right now the best "simple yet portable" method of defining library dependencies.
Pkg-config is especially handy when it comes to cross-compiling, or when you have a special need for a static build instead of shared libraries.
Yet it forgets the MOST important thing: make uninstall.
Nothing worse than software where I have to reverse engineer a makefile in order to uninstall!
make install INSTALL_PREFIX=/opt/lzfse
or make install INSTALL_PREFIX=$HOME/.../lzfse
Uninstalling is then as simple as: rm -r /opt/lzfse
For convenience, I either add /opt/lzfse/bin to $PATH, or create a symlink from /opt/lzfse/bin/lzfse to /usr/bin/lzfse.More generally, I believe that uninstalling, upgrading and related operations are the task of a package manager, not a build script.
Alternative: maintain a HUUUUUGE list of xPATH env variables, and update them every time you recompile something and change the directory in the process. Oh, and hope that other people's Makefiles are intelligent enough to not mess up shit (e.g. use headers from system, and library .so files from your own compile)...
I usually set up a Debian chroot with qemu and compile "natively" (e.g. for RPi). It's dog slow, yes, but at least it works reliably in contrast to cross compilation.
The only way I ever got CC to work is with the buildroot toolchain, which has the downside that it isn't Debian.
Why it forgets that? It's not the job of a library build system to install or uninstall things in your system. It's a job for your system's package manager.
Makefile that has a target (historically called "install") that puts things in appropriate places in a chroot-like manner is just good enough.
Moreover, I tend to see "make install" not as actual installation command (that's indeed the task of a package manager), but more of an "extract the relevant build results".
With that in mind, I prefer packages that have a clear "build destination" folder which is filled (and updated) by a plain "make", so that "make install" isn't even needed anymore, because installation then boils down to a trivial "cp -R" command.
This, of course, requires a disciplined separation of intermediate build results from the final build results. But that shouldn't be too hard. In fact, this may be simpler than writing down a good "make install" in the first place.
Sure, you can install each compiled package to its own subdirectory under /usr/local/, but then their binaries are not located in a default PATH. Whether performed accidentally or intentionally, installing to /usr/local/ prefix (/usr/local/bin/, etc.), should not result in having no way of automating an uninstall of those files.
Most of the executions (those actually used in the wild) of this strategy are quite stupid. Add to that the fact that building packages with distribution's tools is quite easy, and now on top of that add fpm, which produces terrible packages and should be banned for upstream maintainers, but for a desktop installation they're perfectly usable, and checkinstall, which is more than fifteen years old.
> Yes, software devs should be providing a Makefile that is friendly for package managers to wrap.
After what I said to steveklabnik (https://news.ycombinator.com/item?id=12014815): build script (whatever it is, it doesn't need to be makefile) should never touch network when building the project and should not expect libraries in any particular place (especially not the directory with the sources nor $HOME/.whatever). This is enough for build script to be friendly towards package managers and none of the package managers expect anything more from the source tarball.
> But the best software provides a Makefile usable on all platforms it supports, without the expectation that a package manager will be involved.
Yes, of course. But package managers really don't expect anything more than a good build script should provide: no network, no hardcoded library paths, only building the artifacts from the locally accessible sources. And maybe a target that puts the artifacts in appropriate places under $DESTDIR, but this is often optional. There's nothing more than one could do by hand, installing stuff into /opt/$someproject directory, so it can be safely removed altogether, and add symlinks to /usr/local/bin, so they're in typical $PATH.
Package managers actually springed from automating what people did manually just before.
> Whether performed accidentally or intentionally, installing to /usr/local/ prefix (/usr/local/bin/, etc.), should not result in having no way of automating an uninstall of those files.
Let's not make the users mentally disabled people. Do we really need to protect them from all their mistakes? What would be next, adding a recycle bin for files removed with `rm'?
OK, I'm somewhat exaggregating here. But where's the line of that protection?
this could make for a cool portmanteau.
More importantly, who tests it? Most developers using e.g. automake or some other generator don't test this, ever. If you package it, you delegate uninstallation to the package manager.
I'd be very leery of actually running this on my system in case it blew away unrelated bits, or versioned bits shared with other packages or package versions, like shared library symlinks or similar.
Granted, pkgconfig is often a good solution for a hairy problem. I was just so very delighted to see such a simple Makefile :)
As expected whenever a project does this, there's already a PR from someone trying to change that.[0]
And Zstd is not proprietary. (This issue is relevant in this regard: https://github.com/lzfse/lzfse/issues/21)
https://github.com/Cyan4973/zstd
Edit: here is a quick comparison I did on Linux with Project Gutemberg's webster (http://sun.aei.polsl.pl/~sdeor/corpus/webster.bz2).
$ time ./lzfse-master/build/bin/lzfse -encode -i webster -o webster.lzfse
real 0m1.885s
user 0m1.860s
sys 0m0.024s
$ time ./zstd-master/programs/zstd webster -8 -f -o webster.zstd
webster : 25.98% (41458703 =>10772836 bytes, webster.zstd)
real 0m1.700s
user 0m1.660s
sys 0m0.036s
$ ls -l
-rw-r--r-- 1 tyl tyl 12209496 Jul 7 16:26 webster.lzfse
-rw-rw-r-- 1 tyl tyl 10772836 Jul 7 16:31 webster.zstd
$ time ./lzfse-master/build/bin/lzfse -decode -i webster.lzfse -o /dev/null
real 0m0.127s
user 0m0.112s
sys 0m0.012s
$ time ./zstd-master/programs/zstd -d webster.zstd -o /dev/null
webster.zstd : 41458703 bytes
real 0m0.116s
user 0m0.112s
sys 0m0.000s
LZFSE's -h option doesn't show a flag to tweak compression. Zstd's default -1 compression is super-fast, but obviously not optimal. Its -8 is the closest I got to LZFSE's compression speed; its -4 was the closest to LZFSE's compression ratio, with a speed of 0m0.527s real compression, 0m0.101s real decompression.One thing could be that apple's work started before or in parallel with Zstd and didn't know that this was going to be better. But the problem remains we might end up with a compression algorithm widely used (by virtue of being pushed by apple) while another very similar and better algorithm waiting to come to mainstream. Now if there isn't something special about LZFSE in terms of power usage (beyond faster operation reduces power usage) it would be best if once Zstd is really proved to be solid they phase out LZFSE and start pushing this. Don't really think this would happen.
It also seems that Zstd has some dictionary support and so an ever bigger question is whether Zstd can actually replace brotil which probably has a much bigger impact. I really like the idea of Zstd being available everywhere if all the numbers are as good as they seem to be.
(From the previous discussion: https://news.ycombinator.com/item?id=11944975 )
If you see in that previous discussion, I was asking the same questions there and got no real answer. It looks to me like nobody (outside of Apple) has actually tested the energy efficiency.
There is in fact a very high correlation between CPU cycles and energy efficiency, since compression algorithms don't sit idle and use roughly the same instructions. In fact, Yann Collet's Zstd uses the same principles as LZFSE, as both were sprouted from Jarek Duda's research: http://arxiv.org/abs/1311.2540.
The reference LZ4 implementation is absolutely more energy efficient than LZFSE, and in fact Apple does push for its use by offering it in its compression library. However, it tends not to compress as well as both LZFSE and Zstd. For 4G or WiFi (or even broadband), the time lost by transferring more data is not compensated by the time won by decompressing it faster, resulting in much slower downloads than even zlib. LZ4 is still relevant for higher speeds, such as those offered by magnetic hard drives. (Beyond a certain speed, such as for SSDs, compression no longer offers a benefit, but you might be ok with the slowdown given that you win drive space.)
There is a separate discussion to be had about the fact that the open-sourced LZFSE reference implementation is not the one they use (which explains how little they touched it since), as it does not even have ARM-specific code. Also, LZFSE does not claim to be patent-unencumbered. LZ4 and Zstd do have optimized code for ARM.
All in all, it is not a stretch to assume that Apple benefits from this FUD, which explains why there is no comparative benchmark anywhere to be found on their GitHub or in their documentation. It really looks like Zstd is better all around.
I think once Zstd gets a bit more mainstream the best they would do is add it as an option.
"This is a reference C implementation of the LZFSE compressor introduced in the Compression library with OS X 10.11 and iOS 9."
My guess is that the versions on Mac OS X and iOS are different, and aren't even written in C (they might have started as C programs, but would have been hand-optimized later)
> time ./lzfse -encode -i webster -o webster.lzfse
real 0m0.945s
user 0m0.864s
sys 0m0.062s
> time ./lzfse2 -encode -i webster -o webster.lzfse2
real 0m0.803s
user 0m0.715s
sys 0m0.072s
> time ./lzfse -decode -i webster.lzfse -o /dev/null
real 0m0.133s
user 0m0.091s
sys 0m0.036s
> time ./lzfse2 -decode -i webster.lzfse2 -o /dev/null
real 0m0.083s
user 0m0.053s
sys 0m0.025s
So, the version that ships on Mac OS X seems to be faster (10% at encoding, 35% at decoding) than what this source and makefile produce. I don’t think that has to do with my way of building them, as I used the makefile (which uses -Os) to build the original tool, and any compiler flags will not have much effect on the one using the Mac OS X library.Worryingly, the two versions also produce different files (12,209,496 bytes for the GitHub code, 12,234,159 bytes for the library on Mac OS X 10.11.5), but they can decompress each others files and produce the original file.
That is actually pretty common. I would guess the version you have in macOS is an older version. They may have tweaked the algorithm to deliver slightly better compression at the expense of speed, which is always the tradeoff in this field.
On the other hand, I would not be surprised if they prepared special tweaks in their internal version to better support arm64. Strategically, Apple seems to believe that people will stick with building for their App Store if they are pushed to write non-cross-platform code.
(Oddly enough, LZFSE/LZVN seems well-suited for file system compression on hard drives, but here again, Yann Collet wins with its LZ4's superior compression speeds, which impact write speeds.)
if (D == D_prev) {
if (L == 0) {
*q++ = 0xF0 + (x + 3); // XM!
} else {
*q++ = (L << 6) + (x << 3) + 6; // LLxxx110
}
*(uint32_t *)q = literal;
q += L; // non-aligned access OK
} else if (D < 2048 - 2 * 256) {
// Short dist D>>8 in 0..5
*q++ = (D >> 8) + (L << 6) + (x << 3); // LLxxxDDD
*q++ = D & 0xFF;
*(uint32_t *)q = literal;
q += L; // non-aligned access OK
} else if (D >= (1 << 14) || M == 0 || (x + 3) + M > 34) {
// Long dist
*q++ = (L << 6) + (x << 3) + 7;
*(uint16_t *)q = D;
q += 2; // non-aligned access OK
*(uint32_t *)q = literal;
q += L; // non-aligned access OK
} else {
// Medium distance
x += M;
M = 0;
*q++ = 0xA0 + (x >> 2) + (L << 3);
*(uint16_t *)q = D << 2 | (x & 3);
q += 2; // non-aligned access OK
*(uint32_t *)q = literal;
q += L; // non-aligned access OK
}I'm assuming they came up with the mathematical proofs first and translated that into code, so that has something to do with it, correct?
It looks a lot like some crypto algorithms which are a nearly direct translation of the mathematical formulas.
It's not that it's incredibly difficult to follow, but it's just very "math like".
I recently needed an implementation of the Simplex Noise algorithm (that I could port to Common Lisp). I ended up using this one, which works but the code certainly does nothing to help understanding: https://github.com/josephg/noisejs/blob/master/perlin.js
Note that the Javscript implementation is also a port from another language (whose implementation I have failed to find).
Performance was totally unchanged either way. Looks like V8's optimizer eats those vars for breakfast.
var x = a + b;
call(x + c);
and call(a + b + c);
would be literally indistinguishable.And I was a comp sci major first and a physics major second. You spend over a decade doing math with single letter variables. Hard habit to break I guess.
I think it's because in that circle of heavy math coding, there are a different set of well-understood abstractions and shortcuts.
It's no different than saying front-end web developers are not writing readable code because they use $(...) instead of elementMatchingSelector(...) or use functions like xhr() instead of xmlHTTPrequest(). Node.js developers don't think twice about the mechanics of callbacks nor to Erlang developers have any mental block about async message semantics.
Each field has a lingo that has evolved over time, and those who have been in a field for longer tend to make more shortcuts because they are manipulating a concept for the 100th time and are well versed in it.
Sorry for not referencing the original code. The java version I translated from is here: http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise....
And the paper describing the algorithm is here: http://webstaff.itn.liu.se/~stegu/simplexnoise/simplexnoise....
Glad it was useful to you!
I used it as a base for a map generator for a strategy game. Thanks a lot for the code, it worked perfectly.
Of course it technically is programming don't get me wrong but it isn't "make a CRUD app with a simple UI" kind of programming.
D clearly refers to some form of "distance". M is "Medium". L is defined before this snippet of code, but I'd imagine it maps to a concept of Long.
And you're left with the variable 'q'.
The real issue is that unless you are comfortable, code involving pointer math can get confusing (all the * and + can be confusing, especially since they are such overloaded symbols in C). The single character variable names (especially when we're talking about what is basically code representation of mathematical formulae) is hardly a huge issue.
It looks just like the style of code in all the other fast LZ codebases. They are all in this style.
The "non-aligned access OK" comment litter is presumably to silence an LLVM performance sanitizer.
When you look at the code, you use the paper that describes the algorithm as documentation. Using same short one letter variable names in the code and paper makes understanding much easier.
The thing I hate most is when the the paper uses 1-based numbering and the programming language uses 0-based numbering. We should settle for 0-based numbering when describing algorithms.
Look at eg https://www.cs.ox.ac.uk/jeremy.gibbons/publications/arith.pd... to see a cleaner alternative.
(This is about describing algorithms in papers. Optimizing for performance after the big-O has been taken care of is a different matter.)
*(uint16_t *)q = D;
q += 2; // non-aligned access OK
*(uint32_t *)q = literal;
instead of memcpy(q, &D, sizeof(uint16_t));
q += 2; // non-aligned access OK
memcpy(q, &literal, sizeof(uint32_t));
which is better defined behavior (i.e. doesn't violate -fstrict-aliasing) and possibly faster.But I could also say that I'm lazy and it's not a big deal anyway.
C just tends to look like line noise for numerical algorithms sometimes.
} else if (D < 2048 - 2 * 256) {
but if it were grouped with a few more parenthesis it would take a little less time for me to grok it.I think I'll stick with zlib.
What am I missing?
This doesn't take many cycles, but it does take some. While a GOTO is just a jump.
In GCC/MSVC will only (attempt to) inline what you mark as inline. Then MSVC has a keyword which forces inlining. Unless you set a flag which tells the compiler to inline what ever it wants. But that being said Microsoft has a non-POSIX x64 ABI designed to allow better in-lining.
How inlining works starts to dive pretty deep into the compiler rabbit hole.
[0]: https://gcc.gnu.org/onlinedocs/gcc/Inline.html [1]: https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html
Interprocedural register allocation can work better than inlining too, because it keeps the code size smaller, and direct calls have almost no speed penalty.
(If you want to be glib, complain about misplaced FP perhaps?)
Not sure why.
But might be something related, eg like they are talking about a state machine in the description somewhere, and give at most one return per state, or something like that?
So now it will be cross platform?
[1]: https://github.com/jibsen/lzfse/tree/msvc-compatibility
There are really three different compression "markets"; LZMA/XZ provides strong compression while LZFSE provides moderate or light compression so they don't really compete.
To this day I refuse to use the z argument to tar. Packaging and compressing are two different operations, and this is why the UNIX gods created pipes.
And also I'm a stubborn old fart.
https://samsaffron.com/archive/2016/06/15/the-current-state-...
mental note, setup a new dokku box, and try getting this setup...
lzfse:
real 1m44.481s
user 1m17.956s
sys 0m2.852s
lz4:
real 0m28.136s
user 0m1.200s
sys 0m2.240s
lz4 is much faster somehow. The final size are very close.There are two typical speed benchmarks you want to do. For the "compress once, decompress many times" situation, benchmark the time it takes to decompress and ignore compression time. For the "compress once, decompress once" situation, add the compression and decompression times.
The first situation is common for distributing packages and static assets, the second situation is common for distributing dynamic assets.
http://fastcompression.blogspot.com/p/compression-benchmark....
I think a better strategy would be to add support for LZFSE to the browsers themselves.
Yes. This is very performance critical code and I completely see the need to write very optimized code. That's fine. But optimizing code for speed shouldn't imply also optimizing it to use as few characters as possible.
Compression code is code that often runs at boundaries to the external world and thus is a very significant attack surface. To release compression code in a non-safe language is risky enough but then using what amounts to practically write-only code is, IMHO, irresponsible.
At the moment, what's their real alternative? Rust is the only memory-safe language I can think of that could hope to meet their performance requirements, but even the Rust runtime would be a lot of overhead for this application.
That said, I agree this isn't acceptable C code for something that runs on untrusted data while using tons of pointer arithmetic.
But there's nothing stopping you from writing readable C code. That's where my concerns come from.
Code readability is relative to the reader, the programming language, and the conventions of a codebase. That's a lot of things to be relative to! Knowing that ought to put speed bumps on the way to dismissing code one isn't familiar with.
I remember having a reaction years ago on seeing some of P.J. Plauger's C++ standard library code. I think I burst out laughing and said I'd fire anyone who wrote code like that for me. Years of subsequent experience have brought multiple layers of understanding how wrong I was.
I understand it doesn't have a runtime in the way a JIT'ed or GC'ed language has a runtime, but it's a runtime nonetheless.
Asking every client of the compression library to pull in that much overhead would likely make it rather unpopular. Until Rust gets better at eliminating unnecessary parts of the runtime when a program doesn't use it (something like GCC's gc-sections), it's not going to be feasible for small libraries to be written in it.
> (which you need to do in a library used by C anyway, really)
You can also use panic::catch_unwind at the boundary too, it depends on what you want to do.It is possible to significantly optimize that number [0]. Not that binary size is not an issue, but rather 2.4mb vs. 4kb is not an apples to apples comparison
[0]: https://lifthrasiir.github.io/rustlog/why-is-a-rust-executab...
The biggest factor however is that you have to read through that whole page, use unstable features (alloc_system) that condemn you to the nightly, and download and compile musl. This is a huge, brittle pain at the moment, and far from obvious to anyone who comes upon Rust and is thinking of building a C-compatible library using it.
What bits are you thinking of here? Just curious, as I do a lot of no_std work, and don't feel that way, and am probably blind to it :)
Rust 1.10, coming out later today, has a new crate type that removes Rust-specific metadata for dynamic libraries, by the way, making them a bit smaller for this kind of case.
This is fine if you're using no_std for something where these are anathema anyway (writing bare-metal OSes comes to mind) but a huge limitation for a humble user-space library. As it stands if you want to take advantage of Rust's safety you're going to need to reimplement at least Box, probably Vec, and Rc if your program requires that kind of thing. This isn't a huge time suck, but if I were feeling out C-compatible languages before writing a library it would be a major turnoff.
I really like Rust for low-overhead binaries but it is missing a lot when it comes to writing non-rlib libraries.
By the way, you _can_ reintroduce just those things if you want to. no_std means "don't include std", but you can then require them:
#![feature(alloc)]
#![feature(collections)]
#![no_std]
extern crate alloc;
extern crate collections;
use alloc::boxed::Box;
use alloc::rc::Rc;
use collections::vec::Vec;
pub fn foo() -> Box<i32> {
Box::new(5)
}
pub fn bar() -> Rc<i32> {
Rc::new(5)
}
pub fn baz() -> Vec<i32> {
let mut v = Vec::new();
v.push(5);
v
}
Of course, as you can see, the facade crates are largely not stable, so doing this on _stable_ rust isn't quite there yet, which is a thing that matters, as you originally pointed out. I expect as Rust grows for this stuff to stabilize, after all, the std versions are re-exported, so this example is de-facto stable, other than maybe the 'use' lines, which is an easy fix in the future.Thanks for elaborating :)
Or why not use local variables that are guaranteed to be cleaned up?
I suspect Apple would have an alternative to write this in Swift, but that would probably have speed implications (I am guessing).
Ada? Chapel? ATS? D if you avoid the GC?
> That said, I agree this isn't acceptable C code for something that runs on untrusted data while using tons of pointer arithmetic.
Why not? It's not the prettiest code ever, but it gets the point across of what's going on.
It's an implementation of a mathematical algorithm. It doesn't need allTheVariables toBeNamed likeThis. Single letters map to meaningful concepts in the mathematical algorithm.
I don't see how giving the variables longer names would make it more readable. Indeed I think long variable names would obscure the structure.
Code like this has to be looked at in the concept of the algorithm design (which I hope exists...)
I mean, if it weren't sad.
Nope. Because I can't read it. I plainly do not have the information needed to understand what's going on here, nor is the design document part of the source code. As you seem to have no trouble understanding what's going on, can you enlighten me, for example, what's so special about the number 271 that we see M being compared to?
> I don't see how giving the variables longer names would make it more readable.
Variables that are part of the algorithm itself probably make sense in their short name (though that's a common problem with math in general. Being more expressive isn't a bad thing), but there are also parameters to public API that are equally short for no gain.
Also, the code is full of magic numbers and I'm sure an algorithm design document would at least give names out to them.
Besides, even if the document was available (it isn't, at least not to the public), there's absolutely zero harm in making the code more accessible or at least reference the spec.
} else if (D >= (1 << 14) || M == 0 || (x + 3) + M > 34) {
} else if (DirectWeightingFactor >= HUYGENS_LIMIT || MariachiBand == 0 || (xylemNonce + STANDARD_PZSH_INCREMENT) + MariachiBand > SWIM_RATE_B) {
I guess your opinion differs to mine. I like the one that looks like math.
} else if (D >= HUYGENS_LIMIT || M == 0 || (x+3)+M > SWIM_RATE_B)
Code like this also usually can't be edited piecemeal. Any change requires reloading the entire algorithm into your head before a single line is changed. And as others have mentioned, the normal way to do that is to read the paper. .. And if you do that, the paper's naming convention becomes the natural way to express the algorithm.
It's a Cuban Prime. Everybody knows that. Geez!
When the byte is 0xE1...0xEF (inclusive), the bottom 4 bits alone are the count - so after a run of 0xE0 sections this is how you write out any stragglers.
The decoder has comments, but I managed to figure the above out without them, so I doubt it can be that hard - I've just had a fair amount of practice at this bit twiddling C stuff. At some point when writing code you have to assume that the reader will be speaking your language.
https://github.com/lzfse/lzfse/blob/master/src/lzvn_decode_b...
That's not a problem with the code, that's you not understanding the math that's actually happening. For what it's worth, neither do I, but from what I'm gleaning from other comments here, it's a C implementation of a mathematical proof, so it'd be better to go learn the math than expect to be given friendly code. Besides, "open source" doesn't mean "catered to the lowest common denominator" - they don't write code for you or I to understand without some work, we have to earn the knowledge.
And that's because doing the work is more valuable than being a snoot in the style aristocracy.
—Lazy-bones, lazy-bones, would you like a boiled egg?
—Is it peeled off?
—Nope.
—Throw it away.You may ask, "Why and what is q"? By only looking at the fragment posted, where q is the output pointer, I can already guess there is an input pointer named p, and glancing at the full file shows that is indeed the case. x is also a temporary. This is a very common convention.
Sadly, I'm increasingly finding that code these days is some bloated monstrosity with variable names that barely fit into 80 columns and plenty of ridiculous indirection that turns what really needs only a single line into a deep function-call-chain spanning dozens of lines (not all in the same place) and maybe even across multiple files. Reading "modern" C# and Java code makes my head hurt with all the verbiage --- there is so much code, but very little actual substance.
This code is essentially all substance and little verbiage, and I can comprehend it quite easily. Thus I find the style complaints entirely unfounded.
To borrow a sentiment from Linus Torvalds: the code is "unreadable" to you, because you are not (yet) qualified to read it.
/rant
I'd hope you'd at least put a comment header to explain what the parameters actually are for anyone who doesn't have the paper handy, or god forbid, used a paper implementing the same thing using different nomenclature.
You are supposed to have the paper handy. Either you know the paper by heart, or you are actually learning the paper and look at the implementation. Otherwise you really have nothing to do in this piece of code.
Even more mundane codebase are like that. Variable are named in the context of the project. If you have no familiarity with the project variable "user" or "u" means exactly the same to you: nothing.
The difference is that generally regular project are huge in size but simple, while filesystem, compression/encryption algorightm, trading algorithm are tiny but extremely complex. In the former, you use more descriptive naming because people will only have a high level knowledge of the spec. In the later, there is no difference between spec and code, extreme familiarity is necessary to touch the code and naming convention crutches are simply unnecessary.
For example, any person with a modicum of physics background will recognize the following as a kinetic energy calculation, even if I use random letters for the variables.
X = (a * b^2)/2
But if I throw that in a codebase for some web project, I would use fully explicit variable names.This falls under the category of "know the audience for whom you are writing code".
Someone with a physics background might assume that X is the kinetic energy of an object with mass a and velocity b. Or they might assume that X is the displacement of object after having acceleration a for a time b, having initially been at rest. The latter is perhaps more reasonable, since then the choice of two of three three variable names (x and a) is conventional.
A reason why single-letter variable names are practical is that there are strong conventions about what particular variables might represent: eg. start of the roman alphabet is constants, end of the roman alphabet is variables, capital letters are matrices, many letters in the roman and Greek alphabets have one (or a few) common meanings.
(Longer names are for book keeping, not so much for calculation.)
Normally, any sensible 1D metric would be visualized as isolines in that 2D plane. But the "Weissman score" doesn't even make that much sense, it's a discontinuous, non-monotonic function with singularities right in the middle! It doesn't even make sense from a dimensional analysis perspective… the score will fluctuate wildly based on the units you choose for time and size (minutes? seconds? octets? bits?). There is no conceivable real-world application of the Weissman score. It is just a bit of bad math that appeared on TV once.
But Jarek's ANS paper was first out in 2007, and almost no one paid attention to it, because it was plain inscrutable.
Many years later, it took an individual to create FSE (https://github.com/Cyan4973/FiniteStateEntropy), to prove that it could be transformed into something actually useful and competitive. Since then, the paper has been updated a few times, borrowing a few points from FSE in order to become more readable. But it's still very hard to read.
In contrast, FSE code can be copy/pasted.
And all of a sudden, lot of versions have popped out over Internet. By pure chance, they all look like derivatives of FSE or Fabian Giesen's rANS, but they pay tribute to Jarek's ANS paper, because quite clearly it is the source of their work, and prior existence of an actual open source implementation which works and looks pretty damn close to theirs was purely accidental.
This is not paying tribute where it's due.
"Admittedly, LZFSE does not aim to be the best or fastest algorithm out there. In fact, Apple states that LZ4 is faster than LZFSE while LZMA provides a higher compression ratio, albeit at the cost of being an order of magnitude slower than other options available in Apple SDKs. LZFSE is Apple’s suggested option when compression and speed are more or less equally important and you want reduce energy consumption."
https://developer.apple.com/library/ios/documentation/Perfor... has a section titled "Choice of Compression Algorithm"
Apple's libcompression also provides LZ4 if you need it: https://developer.apple.com/library/ios/documentation/Perfor...
Zstd & LZ4 being the work of single developer is amazing though.
Their goal seems to have been to be at least as good as zlib at compressing stuff using less energy and doing it faster (that often correlates quite well with energy use on modern CPUs, as it allows them to drop to low energy states faster)
My guesses would be that they have a simulator that computes/estimates power usage, and that they have CPU setups where they measure power usage directly. I doubt they regularly do the "compress things till you run out of battery" thing that that talk mentions. That takes too long, and cannot be used to measure small changes in power usage.
> Redistribution and use in source and binary forms, with or without modification, are permitted[...]