Created
September 14, 2018 02:15
-
-
Save AlexWayfer/8affba5fa6b4f29b7cc1f22de9e85d9c to your computer and use it in GitHub Desktop.
Ruby Transducers benchmark
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
> ruby test.rb | |
I, [2018-09-14T05:14:28.929558 #30046] INFO -- : true | |
Warming up -------------------------------------- | |
native 1.000 i/100ms | |
baseline 2.000 i/100ms | |
lazy 1.000 i/100ms | |
via_transduce 1.000 i/100ms | |
Calculating ------------------------------------- | |
native 11.293 (± 0.0%) i/s - 57.000 in 5.054247s | |
baseline 21.711 (± 4.6%) i/s - 110.000 in 5.077083s | |
lazy 5.798 (± 0.0%) i/s - 29.000 in 5.009277s | |
via_transduce 3.549 (± 0.0%) i/s - 18.000 in 5.078086s | |
Comparison: | |
baseline: 21.7 i/s | |
native: 11.3 i/s - 1.92x slower | |
lazy: 5.8 i/s - 3.74x slower | |
via_transduce: 3.5 i/s - 6.12x slower | |
Calculating ------------------------------------- | |
native 169.602M memsize ( 0.000 retained) | |
40.000 objects ( 0.000 retained) | |
0.000 strings ( 0.000 retained) | |
baseline 10.216M memsize ( 0.000 retained) | |
10.000 objects ( 0.000 retained) | |
0.000 strings ( 0.000 retained) | |
lazy 52.649M memsize ( 0.000 retained) | |
1.060M objects ( 0.000 retained) | |
0.000 strings ( 0.000 retained) | |
via_transduce 10.236M memsize ( 0.000 retained) | |
340.000 objects ( 0.000 retained) | |
0.000 strings ( 0.000 retained) | |
Comparison: | |
baseline: 10215920 allocated | |
via_transduce: 10235920 allocated - 1.00x more | |
lazy: 52648720 allocated - 5.15x more | |
native: 169601920 allocated - 16.60x more |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'benchmark/ips' | |
require 'benchmark/memory' | |
require 'logger' | |
ARRAY = (0..530000).to_a.freeze | |
def native | |
ARRAY | |
.map { |x| x + 10 } | |
.map { |x| x * 2 } | |
.select { |x| x.even? } | |
.select { |x| x % 5 == 0 } | |
end | |
def lazy | |
ARRAY | |
.lazy | |
.map { |x| x + 10 } | |
.map { |x| x * 2 } | |
.select { |x| x.even? } | |
.select { |x| x % 5 == 0 } | |
.to_a | |
end | |
def baseline | |
ARRAY.each_with_object([]) do |x, result| | |
x = (x + 10) * 2 | |
result << x if x.even? && x % 5 == 0 | |
end | |
end | |
require 'ramda' | |
def transduce(transformation, reducing_fn, initial, input) | |
input.reduce(initial, &transformation.call(reducing_fn)) | |
end | |
PUSHES = -> list, item { list.push(item) } | |
def mapping(&fn) | |
-> reducing_fn { | |
-> result, input { | |
reducing_fn.call(result, fn.call(input)) | |
} | |
} | |
end | |
def selecting(&predicate_fn) | |
-> reducing_fn { | |
-> result, input { | |
if predicate_fn.call(input) | |
reducing_fn.call(result, input) | |
else | |
result | |
end | |
} | |
} | |
end | |
def via_transduce | |
transformation = Ramda.compose( | |
mapping { |x| x + 10 }, | |
mapping { |x| x * 2 }, | |
selecting { |x| x.even? }, | |
selecting { |x| x % 5 == 0 } | |
) | |
transduce( | |
transformation, | |
PUSHES, | |
[], | |
ARRAY | |
) | |
end | |
def logger | |
return @logger if defined? @logger | |
@logger = Logger.new($stdout) | |
@logger.level = Logger::INFO | |
@logger | |
end | |
def test | |
logger.debug native | |
logger.debug lazy | |
logger.debug baseline | |
logger.debug via_transduce | |
result = (native == lazy) && (lazy == baseline) && (baseline == via_transduce) | |
logger.info result | |
result | |
end | |
exit unless test | |
Benchmark.ips do |x| | |
x.report('native') { native } | |
x.report('baseline') { baseline } | |
x.report('lazy') { lazy } | |
x.report('via_transduce') { via_transduce } | |
x.compare! | |
end | |
Benchmark.memory do |x| | |
x.report('native') { 10.times { native } } | |
x.report('baseline') { 10.times { baseline } } | |
x.report('lazy') { 10.times { lazy } } | |
x.report('via_transduce') { 10.times { via_transduce } } | |
x.compare! | |
end |
If you were to throw this up before your test, it'd skew the result quite a bit:
It will not be lazy anymore, this code is not about laziness.
V8 doesn't have optimizations for basic JavaScript functions?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The other thing to consider is that Enumerable methods are all optimized. If you were to throw this up before your test, it'd skew the result quite a bit:
Now if someone could figure out how to write both transduction and composition in a C extension, those numbers would get really interesting. Unfortunately I do not have that skillset currently, but may pursue it later for the sake of amusement.
This JS benchmark was something else: https://jlongster.com/Transducers.js-Round-2-with-Benchmarks
Thanks @konung for the benchmarks!
I'd meant to reply but I was still delving around my own benchmarks to see if there was a clean way to demonstrate a few corner cases, but that does quite nicely.