Skip to content

Instantly share code, notes, and snippets.

@scoroberts
Created October 16, 2019 01:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save scoroberts/a60d61a2cc3afba1e8813b338ecd1501 to your computer and use it in GitHub Desktop.
Save scoroberts/a60d61a2cc3afba1e8813b338ecd1501 to your computer and use it in GitHub Desktop.
Test of different Java based SHA-256 hash implementations
@Grab(group='com.google.guava', module='guava', version='19.0')
@Grab(group='commons-codec', module='commons-codec', version='1.13')
@Grab(group='bouncycastle', module='bcprov-jdk15', version='140')
import groovy.transform.CompileStatic
import java.security.MessageDigest
import java.nio.charset.StandardCharsets
import com.google.common.hash.Hashing
import org.apache.commons.codec.digest.DigestUtils
import org.bouncycastle.crypto.digests.SHA256Digest
//test string for all hashing implementations
String test = "lwkjt23uy45pojsdf;lnwo45y23po5i;lknwe;lknasdflnqw3uo5"
String hashJava(String str){
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] encodedhash = digest.digest(str.getBytes(StandardCharsets.UTF_8));
return bytesToHex(encodedhash)
}
@CompileStatic
String bytesToHex(byte[] hash) {
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if(hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
}
@CompileStatic
String groovyHash(String str){
str.digest('SHA-256')
}
@CompileStatic
String guavaHash(String str){
Hashing.sha256().hashString(str, StandardCharsets.UTF_8).toString();
}
@CompileStatic
String apacheHash(String str){
DigestUtils.sha256Hex(str)
}
@CompileStatic
String bouncyHash(String str){
SHA256Digest digest = new SHA256Digest();
byte[] keyByteArray = str.getBytes(StandardCharsets.UTF_8)
byte[] output = new byte[digest.getDigestSize()];
digest.update(keyByteArray, 0, keyByteArray.length);
digest.doFinal(output, 0);
bytesToHex(output)
}
long iterations = 100_0000
println "Hashing ${iterations} iterations of SHA-256"
//warm up, throw away
for (int x=0; x<1000; x++) hashJava(test)
long start = System.currentTimeMillis()
for (int x=0; x<iterations; x++) hashJava(test)
long end = System.currentTimeMillis()
println ("time java: ${end-start}\t\t${iterations*1000/(end-start)} hashes/sec")
for (int x=0; x<1000; x++) groovyHash(test)
start = System.currentTimeMillis()
for (int x=0; x<iterations; x++) groovyHash(test)
end = System.currentTimeMillis()
println ("time groovy: ${end-start}\t${iterations*1000/(end-start)} hashes/sec")
for (int x=0; x<1000; x++) apacheHash(test)
start = System.currentTimeMillis()
for (int x=0; x<iterations; x++) apacheHash(test)
end = System.currentTimeMillis()
println ("time apache: ${end-start}\t\t${iterations*1000/(end-start)} hashes/sec")
for (int x=0; x<1000; x++) guavaHash(test)
start = System.currentTimeMillis()
for (int x=0; x<iterations; x++) guavaHash(test)
end = System.currentTimeMillis()
println ("time guava: ${end-start}\t\t${iterations*1000/(end-start)} hashes/sec")
for (int x=0; x<1000; x++) bouncyHash(test)
start = System.currentTimeMillis()
for (int x=0; x<iterations; x++) bouncyHash(test)
end = System.currentTimeMillis()
println ("time bouncy: ${end-start}\t\t${iterations*1000/(end-start)} hashes/sec")
@scoroberts
Copy link
Author

Test runs on Intel i5 8th gen laptop:

C:\dev\scripts>groovy hash_comp.groovy
Hashing 1000000 iterations of SHA-256
time java: 2968 336927.2237196765 hashes/sec
time groovy: 2451 407996.7360261118 hashes/sec
time apache: 1025 975609.7560975610 hashes/sec
time guava: 901 1109877.9134295228 hashes/sec
time guava: 1969 507872.0162519045 hashes/sec

C:\dev\scripts>groovy hash_comp.groovy
Hashing 1000000 iterations of SHA-256
time java: 2688 372023.8095238095 hashes/sec
time groovy: 1948 513347.0225872690 hashes/sec
time apache: 867 1153402.5374855825 hashes/sec
time guava: 953 1049317.9433368311 hashes/sec
time bouncy: 1890 529100.5291005291 hashes/sec

C:\dev\scripts>groovy hash_comp.groovy
Hashing 1000000 iterations of SHA-256
time java: 2371 421762.9692113032 hashes/sec
time groovy: 1969 507872.0162519045 hashes/sec
time apache: 916 1091703.0567685590 hashes/sec
time guava: 982 1018329.9389002037 hashes/sec
time bouncy: 2256 443262.4113475177 hashes/sec

C:\dev\scripts>groovy hash_comp.groovy
Hashing 1000000 iterations of SHA-256
time java: 2362 423370.0254022015 hashes/sec
time groovy: 2004 499001.9960079840 hashes/sec
time apache: 841 1189060.6420927467 hashes/sec
time guava: 890 1123595.5056179775 hashes/sec
time bouncy: 1802 554938.9567147614 hashes/sec

C:\dev\scripts>groovy hash_comp.groovy
Hashing 1000000 iterations of SHA-256
time java: 2351 425350.9145044662 hashes/sec
time groovy: 2118 472143.5316336166 hashes/sec
time apache: 999 1001001.0010010010 hashes/sec
time guava: 1135 881057.2687224670 hashes/sec
time bouncy: 2038 490677.1344455348 hashes/sec

@defnull
Copy link

defnull commented Nov 6, 2019

Apache commons-codec does not implement its own MessageDigest algorithms, but uses the standard java implementation. Apache beeing more than twice as fast as java would not make any sense. Whatever your benchmark is measuring, it is not SHA256 performance.

Your warm-up phase is probably way too short. Switching around the order in which the tests are run will significantly change the results.

@scoroberts
Copy link
Author

I thought the same thing and moved them around. I ended up getting the same results -- and consistently the same results. I have also heard that Guava wraps standard, but there too I get better results. If you can find where they get similar results, that would be helpful.

@XZTDean
Copy link

XZTDean commented Jan 31, 2022

I think bytesToHex function makes the performance for MessageDigest and commons-codec different. If make both hashJava and apacheHash return byte[] to avoid transforming to hex string. Then they can get similar results.

@scoroberts
Copy link
Author

scoroberts commented Feb 1, 2022 via email

@scoroberts
Copy link
Author

Took advice to remove the String conversion portions here: https://gist.github.com/scoroberts/77cc2d5c28fa2faeb1626d8eb39ec8c8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment