一直在脑中有这么一个认知:Golang 肯定比 Java 跑得快。毕竟Golang直接编译成目标机器可执行代码,Java 还得在 JVM 中运行。
上周六,舒威同学说,他们做的加解密JAVA实现的TPS在1000左右徘徊,再也上不去了,问我会比C效率差多少。我就想,C嘛肯定效率是最高的,但是不容易写啊,不如用GO试试。我的三观认知中,总觉得 GO 至少比 Java 跑得快吧。
然后周末就选取了常用的加密算法 AES/CBC/PCKS5Padding 来做一下测试吧:

100万次执行加解密简单字符串的时间,不讲究线程啥的,结果显示 GO 比 JAVA 快一倍,好像也符合认知,确实GO要快一溜溜。
今天,我又调整了一下指标,改成每毫秒执行多少次,然后加上多线程(对应 Goroutines),结果 Java 赢了,看来 JIT 还是非常强大的。

注:图片生成来自 在线工具
从图中可以看出,性能在线程数量 5-7 时,是最高的,这个应该与我的机器是6核有关系。
测试代码如下:
import lombok.SneakyThrows;
import lombok.val;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
public class AesBenchmark {
private static ThreadLocal<Cipher> cipher = ThreadLocal.withInitial(AesBenchmark::init);
@SneakyThrows
private static Cipher init() {
return Cipher.getInstance("AES/CBC/PKCS5Padding");
}
@SneakyThrows
public static String aesEncrypt(String src, String key, byte[] ivBytes) {
val keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.US_ASCII), "AES");
val cipher = AesBenchmark.cipher.get();
cipher.init(Cipher.ENCRYPT_MODE, keySpec, new IvParameterSpec(ivBytes));
byte[] encrypted = cipher.doFinal(src.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
}
@SneakyThrows
public static String aesDecrypt(String src, String key, byte[] ivByte) {
val keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.US_ASCII), "AES");
val cipher = AesBenchmark.cipher.get();
cipher.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivByte));
byte[] original = cipher.doFinal(Base64.getDecoder().decode(src));
return new String(original, StandardCharsets.UTF_8);
}
static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static SecureRandom secureRandom = new SecureRandom();
public static String randomString(int len) {
val sb = new StringBuilder(len);
int length = AB.length();
for (int i = 0; i < len; i++) {
sb.append(AB.charAt(secureRandom.nextInt(length)));
}
return sb.toString();
}
private static String src = randomString(100); // 待测试加密解密的明文
private static String key = "B31F2A75FBF94099"; // 加密用的Key 可以用26个字母和数字组成
private static String iv = "1234567890123456"; // 此处使用AES-128-CBC加密模式,key需要为16位。
public static void timesRun(int times) {
byte[] ivBytes = iv.getBytes(StandardCharsets.US_ASCII);
for (int i = 0; i < times; ++i) {
String s = AesBenchmark.src + i;
String e = aesEncrypt(s, key, ivBytes);
String d = aesDecrypt(e, key, ivBytes);
if (!s.equals(d)) throw new RuntimeException("bad");
}
}
public static final int TIMES = 1000000;
@SneakyThrows
public static void routineRun(int threads) {
long start = System.currentTimeMillis();
val service = new Thread[threads];
for (int i = 0; i < threads; i++) {
service[i] = new Thread(() -> timesRun(TIMES));
service[i].start();
}
for (int i = 0; i < threads; i++) {
service[i].join();
}
long millis = System.currentTimeMillis() - start;
System.out.println("avg " + (TIMES * threads / millis) + " times per millis when " + threads + " threads.");
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
routineRun(i);
}
}
}
bingoo@bingodeMacBook-Pro ~/G/x/t/classes> java -version
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
输出
avg 509 times per millis when 1 threads.
avg 1626 times per millis when 2 threads.
avg 2371 times per millis when 3 threads.
avg 2902 times per millis when 4 threads.
avg 3019 times per millis when 5 threads.
avg 3166 times per millis when 6 threads.
avg 3239 times per millis when 7 threads.
avg 3187 times per millis when 8 threads.
avg 3065 times per millis when 9 threads.
avg 3105 times per millis when 10 threads.
avg 3062 times per millis when 11 threads.
avg 2983 times per millis when 12 threads.
avg 2994 times per millis when 13 threads.
avg 2979 times per millis when 14 threads.
avg 2990 times per millis when 15 threads.
avg 3050 times per millis when 16 threads.
avg 3053 times per millis when 17 threads.
avg 2875 times per millis when 18 threads.
avg 3052 times per millis when 19 threads.
avg 3060 times per millis when 20 threads.
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"errors"
"fmt"
"math/rand"
"strconv"
"time"
)
func AesEncrypt(src, key, iv []byte) (string, error) {
block, _ := aes.NewCipher(key)
src = PKCS5.Padding(src, block.BlockSize())
cbc := cipher.NewCBCEncrypter(block, iv)
out := make([]byte, len(src))
cbc.CryptBlocks(out, src)
return base64.StdEncoding.EncodeToString(out), nil
}
func AesDecrypt(dest string, key, iv []byte) ([]byte, error) {
bs, _ := base64.StdEncoding.DecodeString(dest)
block, _ := aes.NewCipher(key)
cbc := cipher.NewCBCDecrypter(block, iv)
src := make([]byte, len(bs))
cbc.CryptBlocks(src, bs)
return PKCS5.Unpadding(src, block.BlockSize())
}
var (
PKCS5 = &pkcs5{}
ErrPaddingSize = errors.New("padding size error")
)
// pkcs5Padding is a pkcs5 padding struct.
type pkcs5 struct{}
func (p *pkcs5) Padding(bs []byte, blockSize int) []byte {
padLen := blockSize - len(bs)%blockSize
padText := bytes.Repeat([]byte{byte(padLen)}, padLen)
return append(bs, padText...)
}
func (p *pkcs5) Unpadding(src []byte, blockSize int) ([]byte, error) {
srcLen := len(src)
paddingLen := int(src[srcLen-1])
if paddingLen >= srcLen || paddingLen > blockSize {
return nil, ErrPaddingSize
}
return src[:srcLen-paddingLen], nil
}
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
letterIdxBits = 6 // 6 bits to represent a letter index
letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
)
var randSrc = rand.NewSource(time.Now().UnixNano())
func RandString(n int) string {
b := make([]byte, n)
// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
for i, cache, remain := n-1, randSrc.Int63(), letterIdxMax; i >= 0; {
if remain == 0 {
cache, remain = randSrc.Int63(), letterIdxMax
}
if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
b[i] = letterBytes[idx]
i--
}
cache >>= letterIdxBits
remain--
}
return string(b)
}
var src = RandString(100)
var key = []byte("B31F2A75FBF94099")
var iv = []byte("1234567890123456")
func timesRun(byes chan bool, times int) {
for i := 0; i < times; i++ {
s := []byte(src + strconv.Itoa(i))
e, _ := AesEncrypt(s, key, iv)
d, _ := AesDecrypt(e, key, iv)
if !bytes.Equal(s, d) {
panic("bad")
}
}
if byes != nil {
byes <- true
}
}
const TIMES = 1000000
func routineRun(routines int) {
from := time.Now()
byes := make(chan bool, routines)
for i := 0; i < routines; i++ {
go timesRun(byes, TIMES)
}
for i := 0; i < routines; i++ {
<-byes
}
millis := time.Now().Sub(from).Nanoseconds() / int64(time.Millisecond)
fmt.Println("avg", int64(TIMES*routines)/millis, "times per millis when", routines, "routines.")
}
func main() {
for i := 1; i <= 20; i++ {
routineRun(i)
}
}
输出:
avg 354 times per millis when 1 routines.
avg 826 times per millis when 2 routines.
avg 1324 times per millis when 3 routines.
avg 1665 times per millis when 4 routines.
avg 1977 times per millis when 5 routines.
avg 1974 times per millis when 6 routines.
avg 1862 times per millis when 7 routines.
avg 1844 times per millis when 8 routines.
avg 1782 times per millis when 9 routines.
avg 1798 times per millis when 10 routines.
avg 1769 times per millis when 11 routines.
avg 1711 times per millis when 12 routines.
avg 1702 times per millis when 13 routines.
avg 1750 times per millis when 14 routines.
avg 1634 times per millis when 15 routines.
avg 1668 times per millis when 16 routines.
avg 1503 times per millis when 17 routines.
avg 1681 times per millis when 18 routines.
avg 1641 times per millis when 19 routines.
avg 1711 times per millis when 20 routines.
bingoo@bingodeMacBook-Pro ~/G/benchwork-aes-go-java> go version
go version go1.11.2 darwin/amd64
我的机器配置:
bingoo@bingodeMacBook-Pro ~/G/benchwork-aes-go-java> system_profiler SPHardwareDataType
Hardware:
Hardware Overview: Model Name: MacBook Pro Model Identifier: MacBookPro15,1 Processor Name: Intel Core i7 Processor Speed: 2.6 GHz Number of Processors: 1 Total Number of Cores: 6 L2 Cache (per Core): 256 KB L3 Cache: 9 MB Memory: 16 GB
JAVA 代码的 POM 文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.bingoohuang</groupId>
<artifactId>aes-benchmark</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
</dependencies>
</project>
extern crate crypto;
extern crate rand;
extern crate rustc_serialize;
use std::thread;
use std::time::Instant;
use crypto::buffer::{BufferResult, ReadBuffer, WriteBuffer};
use crypto::{aes, blockmodes, buffer, symmetriccipher};
use rustc_serialize::base64::{FromBase64, ToBase64, STANDARD};
// Encrypt a buffer with the given key and iv using AES-256/CBC/Pkcs encryption.
fn encrypt(
data: &[u8],
key: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
// Create an encryptor instance of the best performing
// type available for the platform.
let mut encryptor =
aes::cbc_encryptor(aes::KeySize::KeySize128, key, iv, blockmodes::PkcsPadding);
// Each encryption operation encrypts some data from
// an input buffer into an output buffer. Those buffers
// must be instances of RefReaderBuffer and RefWriteBuffer
// (respectively) which keep track of how much data has been
// read from or written to them.
let mut final_result = Vec::<u8>::new();
let mut read_buffer = buffer::RefReadBuffer::new(data);
let mut buffer = [0; 4096];
let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);
// Each encryption operation will "make progress". "Making progress"
// is a bit loosely defined, but basically, at the end of each operation
// either BufferUnderflow or BufferOverflow will be returned (unless
// there was an error). If the return value is BufferUnderflow, it means
// that the operation ended while wanting more input data. If the return
// value is BufferOverflow, it means that the operation ended because it
// needed more space to output data. As long as the next call to the encryption
// operation provides the space that was requested (either more input data
// or more output space), the operation is guaranteed to get closer to
// completing the full operation - ie: "make progress".
//
// Here, we pass the data to encrypt to the enryptor along with a fixed-size
// output buffer. The 'true' flag indicates that the end of the data that
// is to be encrypted is included in the input buffer (which is true, since
// the input data includes all the data to encrypt). After each call, we copy
// any output data to our result Vec. If we get a BufferOverflow, we keep
// going in the loop since it means that there is more work to do. We can
// complete as soon as we get a BufferUnderflow since the encryptor is telling
// us that it stopped processing data due to not having any more data in the
// input buffer.
loop {
let result = try!(encryptor.encrypt(&mut read_buffer, &mut write_buffer, true));
// "write_buffer.take_read_buffer().take_remaining()" means:
// from the writable buffer, create a new readable buffer which
// contains all data that has been written, and then access all
// of that data as a slice.
final_result.extend(
write_buffer
.take_read_buffer()
.take_remaining()
.iter()
.map(|&i| i),
);
match result {
BufferResult::BufferUnderflow => break,
BufferResult::BufferOverflow => {}
}
}
Ok(final_result)
}
// Decrypts a buffer with the given key and iv using
// AES-256/CBC/Pkcs encryption.
//
// This function is very similar to encrypt(), so, please reference
// comments in that function. In non-example code, if desired, it is possible to
// share much of the implementation using closures to hide the operation
// being performed. However, such code would make this example less clear.
fn decrypt(
encrypted_data: &[u8],
key: &[u8],
iv: &[u8],
) -> Result<Vec<u8>, symmetriccipher::SymmetricCipherError> {
let mut decryptor =
aes::cbc_decryptor(aes::KeySize::KeySize128, key, iv, blockmodes::PkcsPadding);
let mut final_result = Vec::<u8>::new();
let mut read_buffer = buffer::RefReadBuffer::new(encrypted_data);
let mut buffer = [0; 4096];
let mut write_buffer = buffer::RefWriteBuffer::new(&mut buffer);
loop {
let result = try!(decryptor.decrypt(&mut read_buffer, &mut write_buffer, true));
final_result.extend(
write_buffer
.take_read_buffer()
.take_remaining()
.iter()
.map(|&i| i),
);
match result {
BufferResult::BufferUnderflow => break,
BufferResult::BufferOverflow => {}
}
}
Ok(final_result)
}
const BASE_SRC: &str = "GyICyfqhJyFFvxpSkYMRaCECDDoteRkdAuEluGxPesfBlWOoYjmhRWJbpNUzXzESSpXXtgbqGEqjGPcPKkXrPeeMbZIBWRXVNHMM";
fn times_run(times: u32, key: &'static [u8], iv: &'static [u8]) {
for x in 0..times {
let _message = format!("{}{}", BASE_SRC, x);
let encrypted_data = encrypt(_message.as_bytes(), &key, &iv).ok().unwrap();
let encrypted_base64 = encrypted_data.to_base64(STANDARD);
let unbase64 = encrypted_base64.from_base64().unwrap();
let decrypted_data = decrypt(&unbase64[..], &key, &iv).ok().unwrap();
assert!(_message.as_bytes() == &decrypted_data[..]);
}
}
fn routine_run(threads: usize, times: u32, key: &'static [u8], iv: &'static [u8]) {
let start = Instant::now();
let mut children = Vec::with_capacity(threads);
for _i in 0..threads {
children.push(thread::spawn(move || times_run(times, key, iv)));
}
for child in children {
let _ = child.join();
}
let elapsed = start.elapsed();
let millis = (elapsed.as_secs() * 1_000) + (elapsed.subsec_nanos() / 1_000_000) as u64;
println!(
"avg {} times per millis when when {} threads",
(threads as u64 * times as u64 / millis),
threads
);
}
fn main() {
let (key, iv) = ("B31F2A75FBF94099".as_bytes(), "1234567890123456".as_bytes());
for i in 1..20 {
routine_run(i, 1000000, key, iv)
}
}
/*
fn create_key_iv() -> ([u8; 16], [u8; 16]) {
let mut key = [0; 16];
let mut iv = [0; 16];
// In a real program, the key and iv may be determined
// using some other mechanism. If a password is to be used
// as a key, an algorithm like PBKDF2, Bcrypt, or Scrypt (all
// supported by Rust-Crypto!) would be a good choice to derive
// a password. For the purposes of this example, the key and
// iv are just random values.
let mut rng = OsRng::new().ok().unwrap();
rng.fill(&mut key);
rng.fill(&mut iv);
(key, iv)
}
*/
运行结果:
bingoo@bingodeMacBook-Pro ~/D/G/rust-aes-workbench> cargo build --release
bingoo@bingodeMacBook-Pro ~/D/G/rust-aes-workbench> target/release/rust-aes-workbench
avg 528 times per millis when when 1 threads
avg 1050 times per millis when when 2 threads
avg 1574 times per millis when when 3 threads
avg 2093 times per millis when when 4 threads
avg 2511 times per millis when when 5 threads
avg 2650 times per millis when when 6 threads
avg 2598 times per millis when when 7 threads
avg 2733 times per millis when when 8 threads
avg 2721 times per millis when when 9 threads
avg 2735 times per millis when when 10 threads
avg 2791 times per millis when when 11 threads
avg 2808 times per millis when when 12 threads
avg 2751 times per millis when when 13 threads
avg 2788 times per millis when when 14 threads
avg 2830 times per millis when when 15 threads
avg 2680 times per millis when when 16 threads
avg 2647 times per millis when when 17 threads
avg 2693 times per millis when when 18 threads
avg 2634 times per millis when when 19 threads
avg 2689 times per millis when when 20 threads
[package]
name = "rust-aes-workbench"
version = "0.1.0"
authors = ["bingoo <bingoo.huang@gmail.com>"]
[dependencies]
rust-crypto = "^0.2"
rand = "0.6.1"
rustc-serialize = "0.3.24"
bingoo@bingodeMacBook-Pro ~/D/G/rust-aes-workbench> rustc --version
rustc 1.29.0-nightly (6a1c0637c 2018-07-23)
bingoo@bingodeMacBook-Pro ~/D/G/rust-aes-workbench> cargo --version
cargo 1.29.0-nightly (506eea76e 2018-07-17)
用 c 做了一下单线程的实现,竟然表现最差,参见结果( gist.github 商业网/bingoohuang/100934dc8561417c6e71daefb43effc8)。
Java 经过这么多代的优化,已经做的很极致了,我觉得 golang 的优势不是性能,体会到的主要是下面:
写业务代码真累,没有沉淀,社区不够强大,做好一个CRUD好难,这方面 J2EE 优势太大

