egonSchiele / contracts.ruby

Contracts for Ruby.
http://egonschiele.github.com/contracts.ruby
BSD 2-Clause "Simplified" License
1.44k stars 82 forks source link

Don't use exceptions for control flow #274

Closed chrisseaton closed 6 years ago

chrisseaton commented 6 years ago

Using exceptions for control flow is slower than using conventional control flow in all implementations of Ruby I tried, and has particularly bad performance on optimising implementations of Ruby. This PR makes each test on a failing method overload 3x faster in MRI, and allows optimising implementations of Ruby like JRuby and TruffleRuby run it up to 10x faster than that.

https://github.com/graalvm/truffleruby/issues/697

nixpulvis commented 6 years ago

Oh noes you've offended RoboCop!

chrisseaton commented 6 years ago

Thanks I fixed it.

nixpulvis commented 6 years ago

Ran the bench.rb with bundle exec ruby benchmarks/bench.rb and got the following results on master and this branch respectively.

master:

testing add                      0.260000   0.000000   0.260000 (  0.260768)
testing contracts add            3.180000   0.000000   3.180000 (  3.180327)
MethodProfiler results for: Contract
+------------------------+----------+----------+--------------+------------+-------------+
| Method                 | Min Time | Max Time | Average Time | Total Time | Total Calls |
+------------------------+----------+----------+--------------+------------+-------------+
| #failure_exception     | 0.015 ms | 0.121 ms | 0.020 ms     | 198.116 ms | 10000       |
| #args_contracts        | 0.001 ms | 0.127 ms | 0.001 ms     | 26.941 ms  | 20000       |
| #maybe_append_block!   | 0.001 ms | 0.143 ms | 0.001 ms     | 10.478 ms  | 10000       |
| #pattern_match?        | 0.001 ms | 0.031 ms | 0.001 ms     | 10.359 ms  | 10000       |
| #ret_contract          | 0.001 ms | 0.016 ms | 0.001 ms     | 10.346 ms  | 10000       |
| #method                | 0.001 ms | 0.025 ms | 0.001 ms     | 20.588 ms  | 20000       |
| #maybe_append_options! | 0.001 ms | 0.017 ms | 0.001 ms     | 10.241 ms  | 10000       |
+------------------------+----------+----------+--------------+------------+-------------+
MethodProfiler results for: Object
+-----------------------------------------------------+----------+----------+--------------+-------------+-------------+
| Method                                              | Min Time | Max Time | Average Time | Total Time  | Total Calls |
+-----------------------------------------------------+----------+----------+--------------+-------------+-------------+
| #contracts_add                                      | 0.155 ms | 0.368 ms | 0.182 ms     | 1822.543 ms | 10000       |
| .__contracts_engine                                 | 0.001 ms | 0.006 ms | 0.002 ms     | 0.173 ms    | 103         |
| #__contracts_ruby_original_contracts_add_j9u7oe5... | 0.001 ms | 0.100 ms | 0.001 ms     | 11.585 ms   | 10000       |
+-----------------------------------------------------+----------+----------+--------------+-------------+-------------+
MethodProfiler results for: Contracts::MethodDecorators
+-------------------------+----------+----------+--------------+------------+-------------+
| Method                  | Min Time | Max Time | Average Time | Total Time | Total Calls |
+-------------------------+----------+----------+--------------+------------+-------------+
| #method_added           | 0.024 ms | 0.234 ms | 0.049 ms     | 2.213 ms   | 45          |
| #singleton_method_added | 0.024 ms | 0.048 ms | 0.030 ms     | 0.273 ms   | 9           |
+-------------------------+----------+----------+--------------+------------+-------------+
MethodProfiler results for: Contracts::Decorator
MethodProfiler results for: Contracts::Support
+----------------+----------+----------+--------------+------------+-------------+
| Method         | Min Time | Max Time | Average Time | Total Time | Total Calls |
+----------------+----------+----------+--------------+------------+-------------+
| .eigenclass_of | 0.001 ms | 0.008 ms | 0.002 ms     | 0.079 ms   | 42          |
+----------------+----------+----------+--------------+------------+-------------+
MethodProfiler results for: UnboundMethod

no-exception-control-flow:

                                     user     system      total        real
testing add                      0.210000   0.000000   0.210000 (  0.205330)
testing contracts add            2.830000   0.000000   2.830000 (  2.831659)
MethodProfiler results for: Contract
+------------------------+----------+----------+--------------+------------+-------------+
| Method                 | Min Time | Max Time | Average Time | Total Time | Total Calls |
+------------------------+----------+----------+--------------+------------+-------------+
| #failure_exception     | 0.015 ms | 0.226 ms | 0.020 ms     | 201.966 ms | 10000       |
| #maybe_append_block!   | 0.001 ms | 0.110 ms | 0.001 ms     | 12.598 ms  | 10000       |
| #maybe_append_options! | 0.001 ms | 0.090 ms | 0.001 ms     | 12.472 ms  | 10000       |
| #ret_contract          | 0.001 ms | 0.159 ms | 0.001 ms     | 12.423 ms  | 10000       |
| #pattern_match?        | 0.001 ms | 0.103 ms | 0.001 ms     | 12.099 ms  | 10000       |
| #method                | 0.001 ms | 0.110 ms | 0.001 ms     | 24.122 ms  | 20000       |
| #args_contracts        | 0.001 ms | 0.037 ms | 0.001 ms     | 23.647 ms  | 20000       |
+------------------------+----------+----------+--------------+------------+-------------+
MethodProfiler results for: Object
+-----------------------------------------------------+----------+----------+--------------+-------------+-------------+
| Method                                              | Min Time | Max Time | Average Time | Total Time  | Total Calls |
+-----------------------------------------------------+----------+----------+--------------+-------------+-------------+
| #contracts_add                                      | 0.158 ms | 0.733 ms | 0.194 ms     | 1943.463 ms | 10000       |
| .__contracts_engine                                 | 0.001 ms | 0.013 ms | 0.002 ms     | 0.218 ms    | 103         |
| #__contracts_ruby_original_contracts_add_j9u7r18... | 0.001 ms | 0.111 ms | 0.002 ms     | 18.388 ms   | 10000       |
+-----------------------------------------------------+----------+----------+--------------+-------------+-------------+
MethodProfiler results for: Contracts::MethodDecorators
+-------------------------+----------+----------+--------------+------------+-------------+
| Method                  | Min Time | Max Time | Average Time | Total Time | Total Calls |
+-------------------------+----------+----------+--------------+------------+-------------+
| #method_added           | 0.025 ms | 0.065 ms | 0.048 ms     | 2.139 ms   | 45          |
| #singleton_method_added | 0.025 ms | 0.044 ms | 0.029 ms     | 0.258 ms   | 9           |
+-------------------------+----------+----------+--------------+------------+-------------+
MethodProfiler results for: Contracts::Decorator
MethodProfiler results for: Contracts::Support
+----------------+----------+----------+--------------+------------+-------------+
| Method         | Min Time | Max Time | Average Time | Total Time | Total Calls |
+----------------+----------+----------+--------------+------------+-------------+
| .eigenclass_of | 0.001 ms | 0.010 ms | 0.002 ms     | 0.095 ms   | 42          |
+----------------+----------+----------+--------------+------------+-------------+
MethodProfiler results for: UnboundMethod

My setup FWIW: deepinscreenshot_select-area_20171110110924

chrisseaton commented 6 years ago

There's a benchmark in the linked issue as well (but with a slightly different patch).

nixpulvis commented 6 years ago

Yea, I just skimmed over that. The bench.rb in this library could use some work. Maybe that's another PR down the pipeline :P

egonSchiele commented 6 years ago

Super interesting, thanks for the PR!