1brc

Version, currently main branch1 version
  • main branchlatestFeb 12, 2024

github.com/nogginly/1brc.cr

Implementation of the "One Billion Rows Challenge" using Crystal.

3 stars
0 dependents
License: CC-BY-4.0

Installation

# Add this to your shard.yml
dependencies:
  1brc:
    github: nogginly/1brc.cr
    branch: main

main is a branch, not a release, so this tracks it rather than pinning a version.

Then run:

shards install

shard.yml

Crystal
>= 1.10
License
CC-BY-4.0
Author
@nogginly
Targets
  • 1brc_serial1 from src/1brc_serial1.cr
  • 1brc_serial2 from src/1brc_serial2.cr
  • 1brc_parallel from src/1brc_parallel.cr
  • 1brc_parallel2 from src/1brc_parallel2.cr
  • 1brc_parallel_ptr from src/1brc_parallel_ptr.cr
  • 1brc_parallel_mmap from src/1brc_parallel_mmap.cr
  • 1brc_parallel_ptr2 from src/1brc_parallel_ptr2.cr
  • 1brc_parallel_ptr3 from src/1brc_parallel_ptr3.cr
  • 1brc_parallel_ptr4 from src/1brc_parallel_ptr4.cr
  • 1brc_parallel_mmap2 from src/1brc_parallel_mmap2.cr
  • 1brc_parallel_mmap3 from src/1brc_parallel_mmap3.cr
  • 1brc_parallel_ptr2b from src/1brc_parallel_ptr2b.cr
  • 1brc_parallel_ptr3b from src/1brc_parallel_ptr3b.cr
  • 1brc_parallel_mmap2b from src/1brc_parallel_mmap2b.cr

Dependencies

This version declares no dependencies.

README

One Billion Row Challenge (1brc) using Crystal

Motivation

While Gunnar Morling originally posed The One Billion Row Challenge for Java developers, over time folks have started implementing it in different languages which are showcased here.

While reviewing the results in Java I ran across one implementation in C that made me wonder what I could do with Crystal.

See discussion in Crystal forum here. This led me to create TODO list of things to try.

See post in Gunnar's "Show & Tell" discussion area here

Overall performance (so far)

Updated 2024 Feb 11

On a PC with AMD Ryzen 7 7735HS CPU, 16 cores, 32 GB, and running Linux, comparing with some other 1brc contenders there's still room to do. See the TODO list for changes so far and possible further improvements. See this CSV for the detailed results.

relativecommandLangmean (s)stddev
1.0xserkan-ozalJava1.16610919542000020.10359078275160513
dannyvankooten/analyzeC1.241372850620.003261490844545827
merykittyunsafe.shJava1.342709938420.036888174169103186
merykitty.shJava1.517705048420.021277280568716347
1.74x slower1brc_parallel_ptr4 16 48Crystal2.030817854020.06009663760371807
1.77x slower1brc_parallel_mmap2b 16 32Crystal2.069155091420.01875921473395469

It's worth noting that there is no significant different between my two best results; given the way the file is read in parallel and in chunks, using mmap or not doesn't make a big difference.

Dependencies

None. Plain ol' Crystal.

Build all

... with shards

  • shards build --release -Dpreview_mt

... with ops

Use if you have brew on macOS or Linux.

First install crops and then

  • ops up
  • ops cbr -Dpreview_mt

Run

StatusImplementationDescriptionPerformance
Fastest1brc_parallel_ptr4Variant of ptr3b using long word name parsingslightly faster
1brc_parallel_ptr3bVariant of ptr3 using optimized hash mapmuch faster
1brc_parallel_ptr3Variant of ptr2 which only launched N fibres and loops over D / N chunks.faster
1brc_parallel_ptr2bVariant of ptr2 using optimized hash mapmuch faster
1brc_parallel_ptr2Variant using page-aligned part size in anticipation of mmap-based implementation.faster again
1brc_parallel_ptrReplaces Slice with Pointer to the buffer, to remove bounds checking when parsing.faster
Fastest1brc_parallel_mmap3Variant of mmap2b using long word name parsingslightly faster
1brc_parallel_mmap2bVariant of mmap2 using optimized hash mapmuch faster
1brc_parallel_mmap2Variant of mmap which only launched N fibres and loops over D / N chunks.slightly faster but not always
1brc_parallel_mmapUsing mmap to load the filefaster
1brc_parallel2Variant using page-aligned part size in anticipation of mmap-based implementation.faster
1brc_parallelParallel multi-threaded implementation that chunks up the file and spawns fibres to process themmuch faster
1brc_serial2Serial implementation optimized to use byte slices (Bytes)a little faster, but still slow.
Slowest1brc_serial1A very simple serial implementation using String linesslowest.

While you are welcome to run the serial implementatios, my focus from now on will on the parallel implementations.

The parallel implementations,

  • given the buffer division is D (via BUF_DIV_DENOM), and
  • given the number of threads is N (via CRYSTAL_WORKERS), and
  • given N < q where q is the number of chunks based on file_size / (Int32::MAX / D)

works as follows:

  • spawns q fibres, and
  • allocates N buffers, and
  • processes N chunks concurrently.

Note that since D is the denominator, we have the following number of chunks (and their sizes) based on D.

Dq chunkschunk size
426512 MB
533410 MB
639341 MB
746293 MB
852256 MB
1278171 MB
16104128 MB
2415685 MB
3220864 MB
4831243 MB

A script run.sh is provided to conveniently run one of the implementations and specify the concurrency values.

Make sure you have measurements.txt in the current folder, and then execute ./run.sh 1brc_parallel 32 24 to run the implementation with the specific threads (32) and buffer division (24).

If your machine is different from mine (see results below), send me your results.

Results

See the various file in /perfdata for results and analyses over time.

Contributing

Bug reports and sugestions are welcome.

This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

This project is available as open source under the terms of the CC-BY-4.0 license.

Contributors