New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add GCC to runtime dependencies #127

Merged
merged 1 commit into from Nov 29, 2018

Conversation

Projects
None yet
3 participants
@schneems
Copy link
Contributor

schneems commented Nov 19, 2018

Ruby 2.6.0 ships with a JIT (just in time compiler) that utilizes GCC (or any other cc such as clang) at runtime. This version of Ruby will be released on December 25th, 2018 and Heroku needs to provide support for this feature out of the box.

Background

The design alternatives can be found in this document

https://docs.google.com/document/d/1q5yEpRddUU2JPynESmffI_BpLDW4XaBgO7iyJE-mwio/edit?usp=sharing

This PR

This PR adds the gcc package to Heroku-16 and Heroku-18 runtime images.

Size cost

Adding the gcc package increases the runtime stack image by about 84mb:

Before adding gcc

-----> Size breakdown...
# ...
       ubuntu:18.04                      85.8MB
       heroku/heroku:18                  428MB
       heroku/heroku:18-build            805MB

After adding gcc

-----> Size breakdown...
# ...
       ubuntu:18.04                      85.8MB
       heroku/heroku:18                  512MB
       heroku/heroku:18-build            805MB

It looks like this works with only gcc, however if we end up needing build-essential then the cost would increase by 64MB to 576MB.

Testing JIT on the stack images testing

On a currently running Heroku-18 dyno with Ruby 2.6:

$ hs run bash -a infinite-everglades-58502
~ $ ruby -v
ruby 2.6.0preview3 (2018-11-06 trunk 65578) [x86_64-linux]
~ $ ruby -e "def a; end; def b; end; a; a; a; a; a; a; a;  puts 'done'" --jit --jit-verbose=1
MJIT: Error in execv: /usr/bin/gcc
MJIT warning: Making precompiled header failed on compilation. Stopping MJIT worker...
done

Note: The warnings in the output indicate gcc could not be found

On a local docker image with gcc available:

$ bin/build.sh heroku-18 heroku/heroku:18 heroku/heroku:18-build
# ...
$ docker run -i -t heroku/heroku:18 /bin/bash
root@4437f834d502:/# mkdir -p vendor/ruby-2.6.0
root@4437f834d502:/# cd vendor/ruby-2.6.0
root@4437f834d502:/vendor/ruby-2.6.0# curl https://heroku-buildpack-ruby.s3.amazonaws.com/heroku-18/ruby-2.6.0.tgz -s -o - | tar zxf -
root@4437f834d502:/vendor/ruby-2.6.0# bin/ruby -e "def a; end; def b; end; a; a; a; a; a; a; a;  puts 'done'" --jit --jit-verbose=1
done
Successful MJIT finish

Note: The output indicates that MJIT was successful

For more output you can also apply --jit-wait which will force mjit to run synchronously (instead of in a background thread).

cc/ @hone @hunterloftis

Add GCC to runtime dependencies
Ruby 2.6.0 ships with a JIT (just in time compiler) that utilizes GCC (or any other `cc` such as clang) at runtime. This version of Ruby will be released on December 25th, 2018 and Heroku needs to provide support for this feature out of the box.

## Background

The design alternatives can be found in this document

https://docs.google.com/document/d/1q5yEpRddUU2JPynESmffI_BpLDW4XaBgO7iyJE-mwio/edit?usp=sharing

## This PR

This PR adds the `gcc` package to Heroku-16 and Heroku-18 runtime images.

## Size cost

Adding the `gcc` package increases the runtime stack image by about 84mb:

Before adding `gcc`

```
-----> Size breakdown...
# ...
       ubuntu:18.04                      85.8MB
       heroku/heroku:18                  428MB
       heroku/heroku:18-build            805MB
```

After adding `gcc`

```
-----> Size breakdown...
# ...
       ubuntu:18.04                      85.8MB
       heroku/heroku:18                  512MB
       heroku/heroku:18-build            805MB
```

It looks like this works with only `gcc`, however if we end up needing `build-essential` then the cost would increase by 64MB to 576MB.

## Testing JIT on the stack images testing

On a currently running Heroku-18 dyno with Ruby 2.6:

```
$ hs run bash -a infinite-everglades-58502
~ $ ruby -v
ruby 2.6.0preview3 (2018-11-06 trunk 65578) [x86_64-linux]
~ $ ruby -e "def a; end; def b; end; a; a; a; a; a; a; a;  puts 'done'" --jit --jit-verbose=1
MJIT: Error in execv: /usr/bin/gcc
MJIT warning: Making precompiled header failed on compilation. Stopping MJIT worker...
done
```

> Note: The warnings in the output indicate gcc could not be found

On a local docker image with `gcc` available:

```
$ bin/build.sh heroku-18 heroku/heroku:18 heroku/heroku:18-build
# ...
$ docker run -i -t heroku/heroku:18 /bin/bash
root@4437f834d502:/# mkdir -p vendor/ruby-2.6.0
root@4437f834d502:/# cd vendor/ruby-2.6.0
root@4437f834d502:/vendor/ruby-2.6.0# curl https://heroku-buildpack-ruby.s3.amazonaws.com/heroku-18/ruby-2.6.0.tgz -s -o - | tar zxf -
root@4437f834d502:/vendor/ruby-2.6.0# bin/ruby -e "def a; end; def b; end; a; a; a; a; a; a; a;  puts 'done'" --jit --jit-verbose=1
done
Successful MJIT finish
```
 
> Note: The output indicates that MJIT was successful

For more output you can also apply `--jit-wait` which will force mjit to run synchronously (instead of in a background thread).

cc/ @hone @hunterloftis
@schneems

This comment has been minimized.

Copy link
Contributor

schneems commented Nov 19, 2018

Also looks like you can get a return status code by trying to interact with MJIT internals if it's not active. On current Heroku 18 dyno:

$ hs run bash -a infinite-everglades-58502
~ $ ruby --jit -e "RubyVM::MJIT.pause(wait: false)" ; echo $?
Traceback (most recent call last):
	1: from -e:1:in `<main>'
-e:1:in `pause': MJIT is not enabled (RuntimeError)
1

@dmathieu dmathieu requested a review from heroku/lifecycle Nov 19, 2018

@dmathieu

This comment has been minimized.

Copy link
Member

dmathieu commented Nov 29, 2018

For the record, @schneems and I have been discussing possibly using musl instead of GCC to reduce impact.
Richard has been poking the MJIT maintainers about that idea.

If you want to use musl for MJIT, of course ruby binary should be linked against musl as well. It may have difference in that layer (can musl's malloc be configured with MALLOC_ARENA_MAX? is it fast and memory efficient? etc). I would use Optcarrot to measure pure-VM performance and to compare it with JIT.


musl is not currently tested and the maintainer is worried about differences in performance and configuration
also Ruby would need to be recompiled with the same cc that mjit uses, so we would need to re-tool to build 2.6.0+ with musl.

Our thinking now is therefore that the slug size reduction isn't worth the fairly large effort it would be to switch to musl instead of gcc.

@dmathieu dmathieu merged commit fcd8ba3 into master Nov 29, 2018

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
heroku/compliance All requirements completed. Reviewed by dmathieu.
Details

@dmathieu dmathieu deleted the schneems/gcc-2 branch Nov 29, 2018

@schneems

This comment has been minimized.

Copy link
Contributor

schneems commented Nov 29, 2018

Thanks 🙏 ! When will this be released so I can test and verify everything works in prod?

@dmathieu

This comment has been minimized.

Copy link
Member

dmathieu commented Nov 30, 2018

Next week.

@dmathieu

This comment has been minimized.

Copy link
Member

dmathieu commented Dec 3, 2018

@schneems

This comment has been minimized.

Copy link
Contributor

schneems commented Dec 3, 2018

Confirm that it works! Thanks ❤️

~ $ ruby --jit -e "RubyVM::MJIT.pause(wait: false)" ; echo $?
0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment