Comments 38
Ответ на вопрос в заголовке вроде бы и так очевиден.
К слову benchmarksgame.alioth.debian.org/u64q/compare.php?lang=go&lang2=yarv
К слову benchmarksgame.alioth.debian.org/u64q/compare.php?lang=go&lang2=yarv
Спасибо за ссылку. Моя статья не претендует на такой объем исследования и сравнения. Только лишь небольшая частичка.
Если уж вы оптимизируете такие вещи, то, имхо, лучше взять md5, вместо sha1, он вроде как быстрее считается. А еще лучше что-нить менее криптографическое, но с равномерным распределением, типа crc32, и не будет проблем с размером int.
Согласен с вами, но исторически сложилось, что используется именно sha1. Поэтому и на Go приходится с ним работать.
В случае с Ruby используется OpenSSL (если библиотека установлена), а это значит, что при подсчете SHA1 и MD5 используются нативные инструкции процессора (если он поддерживает AES instruction set и версия OpenSSL > 1.0.1) и особой разницы нету. В данном случае, большая часть времени расходуется на аллокацию объектов в MRI.
С другой стороны, реализация SHA1 в Go написана на самом Go (даже без Go asm). Так что трудно сказать без профилирования насколько Go реально быстрее в этом случае.
С другой стороны, реализация SHA1 в Go написана на самом Go (даже без Go asm). Так что трудно сказать без профилирования насколько Go реально быстрее в этом случае.
про реализацию на Go — github.com/golang/go/tree/master/src/crypto/sha1, файлики *.s это ассемблеры
в Go в этом тесте тоже процентов 30 времени как раз на аллокацию и конкатенацию строк тратится
в Go в этом тесте тоже процентов 30 времени как раз на аллокацию и конкатенацию строк тратится
О файлики эти пропустил, да. В любом случае, в ассемблерных файлах нету упоминания Intel SHA Extensions (выше я почему-то посчитал, что поддержка SHA быть в AES Instruction Set, но она реализована как часть SSE), а значит все равно некоректно сравнивать эти реализации только по общему времени исполнения. Надо смотреть на реальное время выполнения каждой функции/метода.
Боретесь за миллисекунды и используете SHA1?
Попробуйте code.google.com/p/xxhash, в руби точно есть оббертка.
п.с. сверху опередили
Попробуйте code.google.com/p/xxhash, в руби точно есть оббертка.
п.с. сверху опередили
> Но на Ruby такой трюк выполнить не удалось.
Возможно все
Возможно все
text = 'asd'
Digest::SHA1.digest(text)[-1].ord & 0x7 == Integer('0x'+Digest::SHA1.hexdigest(text))%8 # true
UFO just landed and posted this here
Точно. Возможно, что в вашем случае какой-то быстрый процессор используется. Я у себя тестировал на обычном ноутбуке.
И обратите внимание сколько памяти потребляется в вашем случае. В среднем 20 MiB и для hhvm почти 80! У меня Go 1,5, а Ruby 2,6 МБ использовали.
И обратите внимание сколько памяти потребляется в вашем случае. В среднем 20 MiB и для hhvm почти 80! У меня Go 1,5, а Ruby 2,6 МБ использовали.
То, что PHP иногда быстрее Go, уже были примеры: github.com/dimroc/etl-language-comparison/pull/12
Код Go с github
[ `go run test.go` | done: 1.933083629s ] node1: 957075514 node2: 743407604
[ `go run test.go` | done: 1.933083629s ] node1: 957075514 node2: 743407604
про Go
~30% времени тратится на аллокации строк в цикле
также используйте sha1.Sum([]byte(text)) это будет быстрее:
mas := sha1.Sum([]byte(text))
return int64(mas[len(mas)-1]) % 8
также если вы не используете в коде горутины то GOMAXPROCS на вашем коде никак не скажется
ну и тесты производительности в Go принято делать через bench (http://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go)
~30% времени тратится на аллокации строк в цикле
также используйте sha1.Sum([]byte(text)) это будет быстрее:
mas := sha1.Sum([]byte(text))
return int64(mas[len(mas)-1]) % 8
также если вы не используете в коде горутины то GOMAXPROCS на вашем коде никак не скажется
ну и тесты производительности в Go принято делать через bench (http://dave.cheney.net/2013/06/30/how-to-write-benchmarks-in-go)
оригинальный код:
node1: 1118402612
node2: 772230494
использование sha1.Sum (без пре аллокации)
node1: 837969082
node2: 485179839
пре-аллоцированные данные
node1: 738665390
node2: 478114103
пре-аллокация и sha1.Sum:
node1: 545527947
node2: 286142563
node1: 1118402612
node2: 772230494
использование sha1.Sum (без пре аллокации)
node1: 837969082
node2: 485179839
пре-аллоцированные данные
node1: 738665390
node2: 478114103
пре-аллокация и sha1.Sum:
node1: 545527947
node2: 286142563
Интерпретируемый против компилируемого. Действительно, что же быстрее…
Стоит ли вобще начинать сравнивать две платформы если оригинальный код настолько глуп (я только про код). Хэшировать строку в big integer, конвертировать в hex string, конкатенировать с «0x», парсить обратно в число О_о Чудовищный подход.
crc32, у которого более подходящий интерфейс для этого (возвращает число), работает в 5.5 раза — о, чудо — быстрее:
crc32, у которого более подходящий интерфейс для этого (возвращает число), работает в 5.5 раза — о, чудо — быстрее:
Скрытый текст
require 'digest'
require 'zlib'
max = 2000000
def measure(title)
start = Time.now
yield if block_given?
elapse = Time.now - start
puts "#{title}: #{elapse} sec"
end
measure("with_crc32") {
(0..max).each do |i|
Zlib.crc32("text #{i}") % 8
end
}
measure("with_sha1") {
(0..max).each do |i|
Integer('0x'+Digest::SHA1.hexdigest("text #{i}"))%8
end
}
Можете не переходить на Go, я ваш пример на руби почти в 2 раза ускорил:
require 'benchmark/ips'
require 'digest'
text = 'Preved, medved!!!'
Benchmark.ips do |x|
x.report('habr:') { Integer('0x'+Digest::SHA1.hexdigest(text))%8 }
x.report('my_optimization:') { Digest::SHA1.digest(text)[-1].ord & 7 }
end
===
ruby 1.rb
Calculating — habr: 26.803k i/100ms
my_optimization: 41.454k i/100ms
— habr: 359.737k (± 0.9%) i/s — 1.823M
my_optimization: 646.808k (± 1.1%) i/s — 3.233M
1. Преобразовывать весь хэш в число нет смысла, если вам всего-то надо остаток от деления на 8 узнать
2. Самый быстрый способ узнать остаток от деления — логическое AND с 3 последними битами
require 'benchmark/ips'
require 'digest'
text = 'Preved, medved!!!'
Benchmark.ips do |x|
x.report('habr:') { Integer('0x'+Digest::SHA1.hexdigest(text))%8 }
x.report('my_optimization:') { Digest::SHA1.digest(text)[-1].ord & 7 }
end
===
ruby 1.rb
Calculating — habr: 26.803k i/100ms
my_optimization: 41.454k i/100ms
— habr: 359.737k (± 0.9%) i/s — 1.823M
my_optimization: 646.808k (± 1.1%) i/s — 3.233M
1. Преобразовывать весь хэш в число нет смысла, если вам всего-то надо остаток от деления на 8 узнать
2. Самый быстрый способ узнать остаток от деления — логическое AND с 3 последними битами
Вчера пользователь printercu в комментарии выше как раз и предложил такой вариант ускорения. Он получается почти в 2 раза быстрее чем оригинальный код.
Ещё добавлю:
учитывая что на моём компе редис работает со скоростью 73855.24 requests per second
вы уверены что вам надо оптимизировать именно функцию хеширования?
учитывая что на моём компе редис работает со скоростью 73855.24 requests per second
вы уверены что вам надо оптимизировать именно функцию хеширования?
У меня на серверах есть несколько скриптов на руби, которые с определенной периодичностью обращаются к редису и что-то делают. Например, считают рейтинги игроков, выдают бонусы, чистят старые данные из редиса и т.д.
И я решил переписать часть этих скриптов на Go (чтобы более детально познакомится с языком с прицелом для будущих проектов). В процессе переписывания мне и стало интересно — на сколько Go быстрее Ruby в этой задаче.
Поэтому у меня особой задачи по оптимизации функции хеширования и не было (и так все достаточно быстро работает). Только спортивный интерес, который и перерос в этот топик.
И я решил переписать часть этих скриптов на Go (чтобы более детально познакомится с языком с прицелом для будущих проектов). В процессе переписывания мне и стало интересно — на сколько Go быстрее Ruby в этой задаче.
Поэтому у меня особой задачи по оптимизации функции хеширования и не было (и так все достаточно быстро работает). Только спортивный интерес, который и перерос в этот топик.
Кстати, запустил ваш код на Go у себя на компе, получилось ~1 млн/сек хешей на node2 функции. Для языка с строгой типизацией думал круче будет (видимо там ещё что-то можно улучшить).
Эм. Даже без всякой магии с битовыми масками, вы 25% скорости тратите тупо на конкатенацию строк.
require 'digest'
require 'benchmark'
text = 'Hello world'
N=1_000_000
Benchmark.bm do |x|
x.report{ N.times{ Integer('0x'+Digest::SHA1.hexdigest(text)) } }
x.report{ N.times{ Digest::SHA1.hexdigest(text).to_i(16) } }
end
user system total real
3.360000 0.000000 3.360000 ( 3.373353)
2.600000 0.000000 2.600000 ( 2.593256)
Спасибо, кэп. Вы все прояснили!
Если вам было так очевидно, что не надо конкатенировать строку, чтобы перевести ее в HEX, а пользоваться стандартными методами, то зачем вы это делали?
Ну а серьезно что вы этим бенчмарком показали? Кроме того, что вы плохой ruby разработчик?
Все нормальные разработчики на руби знают, что создавать строку на руби длинее 23 символов лишний раз не надо.
Все нормальные разработчики на руби знают, что создавать строку на руби длинее 23 символов лишний раз не надо.
На Perl получилось:
sub aaa ($text) { return hex( substr Digest::SHA1::sha1_hex($text), -2, 1 ) % 8; } Benchmark: timing 1000000 iterations of node1... node1: 1 wallclock secs ( 0.80 usr + 0.00 sys = 0.80 CPU) @ 1254705.14/s (n=1000000) Rate node1 node1 1254705/s --
Sign up to leave a comment.
Правда ли, что Go быстрее Ruby?