少し前に、話題になったこちらのエントリ。
パスワード問合せシステムを作る (clojureのreducers)
http://qiita.com/kawasima/items/ef75f317605ce800a839
パスワードを求めようとか、JavaのString#formatが遅いとかいろいろ話題がありましたが、これのScala版、Node.js版、そしてものすごくチューニングされたJava版とかを見たりしましが、Groovy版がなかったので書いてみました。
いくつか見てきたエントリを参考にしているので、完全に二番煎じですけれど。
では、書いたコードです。
password-finder.groovy
import java.security.MessageDigest import javax.xml.bind.DatatypeConverter def hexDigest = { s -> def digester = MessageDigest.getInstance('MD5') digester.update(s.bytes) DatatypeConverter.printHexBinary(digester.digest()) } def findPassword = { salt, password -> (1 .. 1000000) .collect { def padded = Integer.toString(it).padLeft(6, '0') [padded, hexDigest(salt + '$' + padded)] }.find { it[1] == password } } def salt = 'hoge' def password = '4b364677946ccf79f841114e73ccaf4f'.toUpperCase() def startTimeMilliSec = System.currentTimeMillis() def answer = findPassword(salt, password) def elapsedTimeMilliSec = System.currentTimeMillis() - startTimeMilliSec println("Answer = $answer, Elapsed Time [$elapsedTimeMilliSec]msec.")
@TypeCheckedとかは付けずにいきます。
結果。
$ groovy password-finder.groovy Answer = [567890, 4B364677946CCF79F841114E73CCAF4F], Elapsed Time [7704]msec.
ところで、MD5から文字列を作る時、最初は自分で
def builder = new StringBuilder() digester.digest().inject(builder) { acc, b -> def binary = b & 0xff if (binary < 0x10) { acc << '0' << Integer.toHexString(binary) } else { acc << Integer.toHexString(binary) } } builder.toString().toUpperCase()
みたいに書いていたのですが、
DatatypeConverter.printHexBinary(digester.digest())
に変えたら2倍くらい速くなってビックリしました…。恐ろしい…。
続けて、並列版。
ここでは、GParsを使いました。
GPars
http://gpars.codehaus.org/
GParsはGroovyに同梱されているので、特に準備なく使用できます。
では、書いたコードです。
password-finder-gpars.groovy
import groovyx.gpars.GParsPool import java.security.MessageDigest import javax.xml.bind.DatatypeConverter def hexDigest = { s -> def digester = MessageDigest.getInstance('MD5') digester.update(s.bytes) DatatypeConverter.printHexBinary(digester.digest()) } def findPassword = { salt, password -> GParsPool.withPool { (1 .. 1000000) .collectParallel { Integer.toString(it).padLeft(6, '0') } .collectParallel { [it, hexDigest(salt + '$' + it)] } .findParallel { it[1] == password } } } def salt = 'hoge' def password = '4b364677946ccf79f841114e73ccaf4f'.toUpperCase() def startTimeMilliSec = System.currentTimeMillis() def answer = findPassword(salt, password) def elapsedTimeMilliSec = System.currentTimeMillis() - startTimeMilliSec println("Answer = $answer, Elapsed Time [$elapsedTimeMilliSec]msec.")
速くならなかったというか、むしろ遅くなりました…。
$ groovy password-finder-gpars.groovy Answer = [567890, 4B364677946CCF79F841114E73CCAF4F], Elapsed Time [9580]msec.
ちなみに、うちの環境だと単純にcollectParallelとかfindParallelに変更するだけじゃなくて、
(1 .. 1000000) .collectParallel { Integer.toString(it).padLeft(6, '0') } .collectParallel { [it, hexDigest(salt + '$' + it)] } .findParallel { it[1] == password }
に分解した方が速かったです。
まあ、それでも元より遅いんですけど…。
環境は
model name : Intel(R) Core(TM)2 Duo CPU P8700 @ 2.53GHz
on Ubuntu Linux on VMware。
やっぱ、貧弱かなぁ?