Building ruby with jemalloc

02 May 2018

There's been a lot of discussion recently about running Ruby with jemalloc. Sounds like some good memory savings to be had basically for free. I ran into an issue initially when compiling Ruby with the --with-jemalloc option. On Mac OSX Sierra, compiling Ruby 2.4.2 from source and using jemalloc 5.0.1, I got this linker error:

... lots of build process output ....
linking miniruby
Undefined symbols for architecture x86_64:
  "_je_calloc", referenced from:
      _rb_objspace_alloc in gc.o
... and much more ....

One fix is a small change to the autoconf script, configure.in. Ruby 2.4.1's configure.in script had this but 2.4.2 didn't:

4211a4212,4214
> AS_CASE(["$with_jemalloc: $LIBS "], [no:* | *' -ljemalloc '*], [],
> 	[LIBS="-ljemalloc $LIBS"])
>

This kind of makes sense. Per the docs, AS_CASE is a macro that generates a case statement and adding -ljemalloc to a list of libraries seems like what's needed here. It results in this change to the generated Makefile, which also seems reasonable:

\79c79
< LIBS = -lpthread -ldl -lobjc $(EXTLIBS)
---
> LIBS = -ljemalloc -lpthread -ldl -lobjc $(EXTLIBS)

And then you can verify jemalloc is linked with:

$ ruby -e "p RbConfig::CONFIG['LIBS']"
"-ljemalloc -lpthread -ldl -lobjc"

Another way to verify it is to run a script under dtruss and the process will attempt to read a jemalloc config file:

$ sudo dtruss -t readlink ruby -e "42"
... some output ...
readlink("/etc/je_malloc.conf\0", 0x7FFF58DAFB80, 0x400)		 = -1 Err#2
... more output ...

If jemalloc isn't linked in, that readlink system call won't happen.

Side note - at first I attempted to verify that jemalloc was included using FFI. I figured I'd just call a function that was declared in jemalloc.c. So I did:

require 'ffi'
module Foo
  extend FFI::Library
  ffi_lib 'libjemalloc.dylib'
  attach_function :je_malloc_usable_size, [ ], :int
end
puts "Usable size: #{Foo.je_malloc_usable_size}"

But then I realized that doesn't make sense. ffi_lib will load up the specified library regardless of whether it's linked into the Ruby process, so that would never fail.

Finally, if you're using Ruby 2.5.1, it's the same change except since this commit the build process uses configure.ac rather than configure.in. But just paste in that same snippet on line 4045, bam done.