HNHacker News
TopNewBestAskShowJobs

StefanKarpinski

5,587 karma · joined November 14, 2010

[ my public key: https://keybase.io/stefankarpinski; my proof: https://keybase.io/stefankarpinski/sigs/QmBtKeGXG8EFVCErRV-VXRykH-gMg2jTEKGq-2xWMMU ]
submissionscomments
StefanKarpinski··on The Accelerating Adoption of Julia
That C code is 39% faster than the Julia version, so faster but not massively so. You can optimize the Julia code to be as fast as the C code by using in-place operations, but it's probably not worth if for such a small speedup unless the code is a real bottleneck. By comparison, the Python version (using NumPy, of course) is 25x slower than C, which is a whole different situation.

Most the speed advantage of the C here is due to reusing the same memory over and over, which you can do pretty easily in Julia as well. Here's the Julia code using in-place operations and it's still quite a bit more readable than the C version: https://gist.github.com/StefanKarpinski/e57f5a36b7890b261a0d.... I timed this and it's the same speed as the C version. When developing this, it follows the same general outline as the C version, but you have several benefits: (1) You can use asserts to compare to the easy version; (2) There are niceties like bounds checks and array indexing. In C you can't do (1) because there is on easy version to compare with. And doing the array index computations in C is kind of a nightmare—it's so easy to accidentally screw them up.

StefanKarpinski··on The Accelerating Adoption of Julia
I'm a pretty competent C programmer, but doing even fairly simple matrix programming in C is fiendishly hard not to screw up. When I was porting this code [1] to this [2], it took an absurdly long time to iron out all the bugs. And just look at the difference in verbosity and difficulty. Worst of all, C has no guard rails: it's so easy to think you have it right, only to realize much later (or not at all, frankly) that you're computing complete nonsense.

[1] https://github.com/JuliaLang/Microbenchmarks/blob/af3d18f7b3...

[2] https://github.com/JuliaLang/Microbenchmarks/blob/af3d18f7b3...

StefanKarpinski··on The Accelerating Adoption of Julia
The core problem with class-based object orientation is that methods go inside of the type instead of being added externally. That means everyone has to agree on what methods can be called on a class or they have to subtype it, which sounds harmless, but is actually a big problem when what you really wanted to do was just define a new method for an existing type. My best attempt at explaining: https://www.youtube.com/watch?v=kc9HwsxE1OY

There's also the issue that one sometimes needs to specialize generic operations in generic algorithms on something other than the receiver—sometimes even on more than one of the arguments. Single dispatch forces you to use something slow and awkward like double dispatch in cases like this. And that's assuming the person who wrote the generic code anticipates the need for specialization! If they don't allow for it, then you're just stuck. With multiple dispatch, you can just define the method you need the specialization for and you're done.

StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
For the historical record, when we picked those benchmarks [1] we were terrible at them. Like 10,000x slower than C. We picked them because they were representative of the kinds of things that we felt a language should be good at: iteration, recursion, shuffling data in arrays, floating-point operations, complex number arithmetic, string operations, small matrix operations and large matrix operations. Then we worked on the language until it was no worse than 2x slower than C. So claiming that we cherry-picked those benchmarks because they made Julia look good simply isn't historically accurate: we picked them because they made Julia look bad, and then we improved Julia until it didn't look bad anymore.

[1] https://julialang.org/benchmarks/

The only faster way to read CSV files in R that I'm aware of is vroom, which is faster because it's lazy: it doesn't parse until you actually access the data. According to vroom's own benchmarks, if you access all the data, it is slower than data.table, which is compared here. So yes, if you want to load a CSV file and not access most of the data, then vroom is faster; if you want to access all of the data (which seems pretty typical), it is not faster.

Regarding "nonstop attacks on other languages", honestly, we don't care and would be happy to stop talking about other languages. However, there is a constant barrage of people making comparisons to other languages and demanding that we justify Julia's existence. Like it is literally offensive to people that Julia exists — "why didn't you just use Python/R/Matlab?!??!" I cannot express how continual this is. So many people have asked to see these exact benchmarks comparing CSV.jl to read.table and Pandas' csv reader. So here are the benchmarks. If you see a mildly worded benchmark comparison showing that one CSV library is faster than some others as "an attack", then you are too good for the internet.

StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
There are two potential issues that this might disregard:

1. cold file cache

2. JIT compile time

The cold/hot file cache issue affects all languages equally, so it doesn’t invalidate the comparison. It is possible that CSV file is not in cache and a system's disk I/O is slow enough that it becomes a bottleneck, which would make all parsers equally slow. However, this is not very realistic because CSV files—especially large enough ones where you care about speed—are almost always compressed—So you're not going to be bottlenecked on disk read. Assuming that compressed disk read + decompression is fast enough to keep the data flow high, you're back to CSV parsing being the bottleneck.

The JIT compile cache only affects Julia. The reason is it not included in the benchmark results is because it is a small, fixed overhead: it is only paid on the first CSV file read (no matter the size), and if you read a larger data set, the compile time does not increase. Since the point of benchmarks is typically to project from a smaller case how long it would take to do even larger tasks, you don't want to include small, fixed overheads.

For some concrete numbers, I just timed reading a tiny CSV file on my MacBook Pro 2018 and the first read, including compile time took 4 seconds. The second read took 0.000347 seconds. So that's the fixed overhead we're talking about here for reading a CSV file: about 4 seconds on the very first CSV file you read. People can be the judge of whether that's a showstopper for them or not.

StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
Vroom is lazy though: it doesn’t parse until you access the data. That’s a very different approach, which is why it’s no direct comparison. You’ll note that data.table, which is compared in the article, is faster than vroom’s default configuration when it comes to whole table scans, which would be the appropriate comparison. If the data is mostly numeric, then data.table is faster for all configurations. That said, if you don’t plan to access most of the data in a data set, then vrooms lazy approach can be as big win.
StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
CSV.jl seems to be proof that you're wrong about that. More concretely, the only difference between parsing to a data frame, parsing to a parquet file, and executing a query over data is a) where you put the data in memory and b) optionally calling some code to operate on the data as you go. It's hard to see how those applications require radically different designs.
StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
> there inherently is no optimal solution to "a problem with large performance-correctness tradeoffs"

You seem to be under the misapprehension that mainstream CSV libraries parse float values with reduced precision in order to go faster. They do not. A decimal floating-point string represents a precise mathematical value and the only acceptable value for a CSV parser to produce is that value correctly rounded to a Float64 (or some other type if requested). Any CSV parser that does anything else is simply incorrect.

> We saw elsewhere in the thread that Julia's float parsing code at least presently handles certain syntaxes incorrectly.

You may be referring to a bug mentioned elsewhere that fields like `-123.` with a leading minus sign and a trailing decimal point are misinterpreted as strings. That is a bug, plain and simple. It has nothing to do with float parsing speed/accuracy tradeoffs.

StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
That you believe this violates the principle of "do one thing and do it well" indicates a certain "abstraction blindness". Do you really believe that "parse CSV to a data frame" and "parse CSV to a parquet file" and "parse CSV and apply a query to it in-place" are completely separate, unrelated things? It seems clear to me in any case that with the right abstraction, those are the same thing composed with different choices of what to do with the parsed data.

At a higher level, this seems like a classic Julia versus Python dynamic. In the Python ecosystem there are a dozen different versions of each thing that make different trade-offs and specialize in different ways. When you're picking one, you have to spend a week evaluating all the possible CSV parsers (for example).

In the Julia ecosystem, the ideal it to have one really high-quality, fast, flexible implementation that everyone collaborates on to make it great for all kinds of use cases. And the language design and the compiler make this possible, since you can write pretty abstract code and compose it with various types and get a really fast implementation. The annoying and tricky logic of parsing the CSV format only needs to be done once, and reused for all different situations.

This isn't done for "macho bragging rights" but because it is a waste of time and effort to implement a dozen half-baked versions of the same thing. When there are a dozen separate implementations of something like this, they also end up disagreeing on their behavior, which causes no end of headaches.

StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
Interestingly, CSV.jl can do all of those. This seems like a classic case of lack of composability in Python: you need a different CSV parser for each and every application even though the core logic of parsing the CSV format is the same. This is because anything fast has to be implemented in very concrete, specialized C code. In Julia, you can write something generic and let the compiler specialize it for you, so you get many different ways of applying the same thing for free.
StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
The fact that the standard CSV reader is not good enough to actually use except in toy applications is itself an issue, frankly.
StefanKarpinski··on CSV Reader Benchmarks: Julia Reads CSVs 10-20x Faster than Python and R
You may be missing the point of the comparison, which is this: to compare the performance of the de facto standard CSV parsers of each system when used the way that a typical user would use them, i.e. to parse a CSV file into a data frame.

Regarding your specific points:

1. Yes, it's possible to rig up some way of parsing in parallel in Python, like splitting the CSV file into multiple files and then parsing them in separate processes and then joining that. But no matter how you do it, you have to jump through extra hoops to do it. And that is not something most users do.

2. I don't entirely follow this point. Perhaps using PyArrow's parser would be faster than what is timed here, but is that what the typical Python data science user would do? Most Python users use Pandas and the CSV parsing that it provides. If that changes, then it would be good to do a new benchmark.

3. Yes, floating-point decoding is hard, but Julia's is fully accurate, so if it's also faster than Python's how is it an unfair comparison? If Julia was cheating by doing imprecise float parsing, sure, that would be a valid complaint, but it's not.

These "conflations" sound suspiciously like excuses. If float parsing is slow, make it faster. If concrete object decoding is slow, stop doing that. If threading would be faster, bite the bullet and use threading. If the language doesn't provide good support for these things, then maybe the language is at fault.

StefanKarpinski··on Chrome is deploying HTTP/3 and IETF QUIC
If I understand the benefits of HTTP/3 correctly, this post also doesn't address one of the major ones: seamless connection handoff during client mobility. Earlier HTTP versions are TCP-based, so if the IP address of a mobile client changes, e.g. because of moving from one cell tower to another or from wifi to/from cell, then the application layer has to notice the lost connection, create a new connection, and move whatever application-level context there is to the new connection. Not all applications do this, and even when they do, it's slow. If my understanding is correct, with HTTP/3 that's no longer necessary — the same HTTP/3 connection can migrate from one IP address to a different one.

Another benefit that isn't measured in this post but has been mentioned elsewhere in the comments here is that the experience on flaky wireless connections (without changing IPs) should be much better. TCP was designed on the premise that packet loss almost never due to physical failure to transmit a packet, and almost always due to routers queues being full (i.e. network congestion). Wireless networks violate this assumption badly: physical-layer issues are the most likely cause of packet loss on a wireless connection. TCP reacts to wifi packet drops by backing off, assuming that some router is overloaded, but the routers are fine — it's just last hop signal that's bad. In those circumstances, the client should just try again instead of throttling the connection to nothing. Since HTTP/3 uses UDP, it can potentially handle dropped packets more appropriately.

StefanKarpinski··on s7 – Scheme implementation intended as extension language for other applications
Good point — especially since as opposed to full continuations, which are essentially impossible to implement efficiently, coroutines / one-shot continuations, have a completely obvious and intuitive implementation: each coroutine is a separate stack and when one coroutine yields to another, it's just a jump to executing code on a different stack.
StefanKarpinski··on The missing harm of manual dispatch in Julia
The Julia community is large and very active. Julia is also rapidly ascending in various language rankings (19 on IEEE Spectrum, 28 on Tiobe).

It's sort of a random ask, but Qt bindings do exist:

https://juliahub.com/ui/Packages/Qt_jll/s7blD/5.15.0+3

along with a higher level QML wrapper:

https://juliahub.com/ui/Packages/QML/JLkMo/0.6.0

StefanKarpinski··on JuliaDB
My current thinking is that it would make sense to implement the standard DataFrame abstraction (with all the generic functionality it offers) with a couple kinds of distributed data frame. One approach is to make a distributed stack of local data frames; another is to make a single data frame where each column is a distributed vector. Not sure which is better. Fortunately, Julia makes it really easy to experiment with these kinds of things. Of course, as soon as you have a system like that you need a scheduler for work at which point you want something like Dask or Dagger (https://github.com/JuliaParallel/Dagger.jl).
StefanKarpinski··on Codata in action, or how to connect FP and OOP
A subtlety we've discovered through success and growth of Julia is that even if your language has some solution to the expression problem, if it's has non-zero cost and isn't the default then people won't actually use it, which undermines much of the benefit. Systems like CLOS, Clojure and even R, which have multimethods but where they're opt-in and use slow hash-based implementations, don't in practice see most of the benefits that we've seen in Julia from multiple dispatch in terms of code reuse and composability. Another way to say this is that if multiple dispatch is just a feature that you reach for when you need to do something fancy, then it looks like just another feature; when it's ubiquitous it becomes transformative.
StefanKarpinski··on JuliaDB
It was originally a working title and then we just never found a name we liked better, but yeah, that is pretty funny.
StefanKarpinski··on JuliaDB
I would add that while JuliaDB is really great for some applications, the data ecosystem is very much coalescing around DataFrames, which has recently caught up in performance with JuliaDB and has a broader more flexible API. (I'm one of the original authors of JuliaDB, so this is not a knock on the package at all.)
StefanKarpinski··on C.I.A. Lacing (2014)
Take a picture, it will last longer
StefanKarpinski··on JuliaCon2020: Julia Is Production Ready
With a bit more detail, this would make a great blog post!
StefanKarpinski··on JuliaCon2020: Julia Is Production Ready
For services we deploy at Julia Computing, we use docker containers since that's just the way devops is done these days, but we also use manifest files to pin all our dependencies which almost makes dockerizing things redundant. It doesn't hurt to have a couple of layers ensuring reproducibility.
StefanKarpinski··on JuliaCon2020: Julia Is Production Ready
Julia follows semantic versioning [1] and is now on version 1.5, which means there been five non-breaking releases introducing new features (and dozens of bugfix only patch releases). Code that worked on 1.0 also works on 1.1, 1.2, 1.3, 1.4 and 1.5 and will keep working in 1.6, etc. until such a time as a breaking 2.0 release is made. Even then breaking changes will be kept minimal and only made to enable worthwhile language features.

[1] https://semver.org

StefanKarpinski··on JuliaCon2020: Julia Is Production Ready
Another example is that all the infrastructure for serving Julia packages to Julia users in the new 1.5 release is implemented in Julia. This is a content distribution network served by highly concurrent servers over HTTPS. The systems interacts with git servers, GitHub APIs and AWS S3 among many other moving pieces. Code can be found here: https://github.com/JuliaPackaging/PkgServer.jl.

Writing servers in Julia is really pleasant thanks to the clean coroutine-based task model. Under the hood, Julia uses libuv to get efficient high-concurrency I/O with an event loop. But from the user's perspective, you just write simple blocking code and use `@sync` and `@async` to spawn concurrent tasks. If you want to use multiple threads, just use `@spawn` instead of `@async`. This is very similar to Go's highly successful I/O and concurrency model.

StefanKarpinski··on Julia 1.5 Highlights
That is truly the dumbest critique of Julia that's been published. So dumb I didn't bother refuting it at the time, but apparently people take it seriously?

- Hello world is slow? Really? Yeah, it's not a hello-world-oriented programming language.

- One-based indexing is questionable? Troll much?

- Using `ccall` is unsafe? No kidding, there's no way to call a shared library that doesn't crash if you lie to it.

- `@printf` sucks? Then don't use it. I never do. The `print` function works for 99% of cases.

- Julia development is slowing down? Whiffed on that call.

Regarding the C ffi point, you can use Clang.jl to generate correct bindings so that you don't have to read C header files. But note that if you want to call a function in C you need to look at and match its signature, so it's not all that onerous to have to do the same from Julia. Julia's C interop is so good that several other languages have copied it since it came out.

StefanKarpinski··on Julia 1.5 Highlights
Julia has utterly standard lexical scoping—and always has. This issue has nothing to do with lexical vs dynamic scoping, it has to do with whether an assignment in a loop clobbers a global (convenient but dangerous) or creates a loop-local variable (safe but sometimes inconvenient). Have you read the linked manual section on scope? It is very clear and explains the design considerations quite thoroughly.
StefanKarpinski··on Julia 1.5 Highlights
As noted in the rewritten scope docs at https://docs.julialang.org/en/v1/manual/variables-and-scopin...:

> An important property of this design is that any code that executes in a file without a warning will behave the same way in a fresh REPL. And on the flip side, if you take a REPL session and save it to file, if it behaves differently than it did in the REPL, then you will get a warning.

So there's no real danger of deviating behavior unless you're in the habit of ignoring warnings.

As to why bringing back 0.x scope rules is not a good idea, this post explains why it was changed in the first place: https://discourse.julialang.org/t/explain-scoping-confusion-.... In short, for non-toy programs, it's a significant source of hard-to-find bugs.

Also: changing this behavior in non-interactive contexts would violate the semantic versioning compatibility commitment of 1.x releases. Of course, 1.5 does sometimes introduce a warning in existing code, but (a) it will keep working the same way it did only with a warning and (b) this only happens if you had a global implicitly shadowed by a local, in which case there's a large chance your code was actually broken.

Give it a try. A lot of time and thought was put into this change and we believe the new design is pretty much locally optimal. It preserves the safety of the 1.0 behavior for non-interactive programming while recovering the convenience of the 0.x behavior in the REPL.

StefanKarpinski··on Julia 1.5 Highlights
That’s a good idea, although this is a highlights blog post, not intended to list all the changes. For that you can look at the 1.5 release notes:

https://docs.julialang.org/en/v1.5/NEWS/

StefanKarpinski··on Benchmark of popular graph/network packages v2
This is a great analysis of popular graph & network analysis frameworks. The TLDR is that Julia's LightGraphs package wins all computational benchmarks and is only bested on I/O (basically because no one has worked on I/O speed yet).
StefanKarpinski··on Julia library for fast machine learning
This video is my best attempt to explain it (not a rickroll, I swear): https://youtu.be/kc9HwsxE1OY
← PreviousPage 6 of 32Next →