CLOVER🍀

That was when it all began.

Re: Groovy: パスワード問合せシステムを作る

少し前に、話題になったこちらのエントリ。

パスワード問合せシステムを作る (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。

やっぱ、貧弱かなぁ?