クーの自由研究

マスターのかえるのクーは、弟子達の召喚術により新たな依り代を得てⅡ世として復活しました。

Javaが用もないのにマルチスレッド並列動作して高速化する事案について

いつから?どうしてこうなった~?

こんにちわ、こんばんわ。かえるのクーの助手の「井戸中 聖」(いとなか セイ)でございます。

たまたまJavaで試作していたプログラムが、Javaにしては非常識に速いので、他の言語と比べてみることにしました。

中井かんいち on Twitter: "藤子  不二雄Ⓐ先生には大変申し訳ないが、実写版の忍者ハットリくんは、夜道で出会ったら卒倒するのではないかと思う。  https://t.co/Yi0FHLcjaz" / Twitter

 

比較のため、ポイントにだけにそぎ落としています

・簡単なオブジェクト(インスタンス)を4億個準備する。(パラメータ値あり)

・オブジェクトをハッシュ(もしくは連想配列)に格納する。

・上記データの準備にどれくらいかかるか測定する。

・キャッシュの影響をすくなくするため、そのまま30秒待つ

4億個のインスタンスを順次取得してパラメータ値を加算する。

・加算した値を表示する。

・加算にかかる時間を計測する。

 

結果:

測定 Java Python※

Python※

(Pypy)

Rust C/C++
データ準備(sec) 5.392 179.065 101.179 79.219 166.923

データ集計

(sec)

1.619 18.055 2.269 61.565 88.702
所感 えっ ?!?! よくできました?
まぁまぁ!
(元々Pythonの2~10倍速い感触)
優秀なはずでは? 連想配列苦手?

※Pythonではメモリ不足でswapが発生するので半分の2億個にしています。

もちろん、数字が小さい方が速いんですよ!(数字と言語みると誤解しそう。)

Javaのデータ準備が10Sec以内なのは異常。並列処理でもしない限り無理!→並列処理してるっぽいです。

Java:本当に全コア使って総力戦してます。。。これなら10Sec切るかぁ。でもHadoop / spark もThreadも使ってないんだよ。(sleepにはThread使ってるけどぉ)

Python:100%近くとなっているのは、その時点でどれか1個だけ。Pythonですから。

numpy使ってませんから。。

Rust:100%近くとなっているのは、その時点でどれか1個だけ。マルチスレッドコーディングしてませんから。(もちろんリリース/最高オプティマイズでの実行です)

Rustの連想配列って、Pythonより遅いんだ。。。がんばれ!

Rustが予想外に(集計が)遅いので、C / C++でやってみたくなりました。

今使ってないんで、やるならVisualStudio入れるところからです。

(追記)速度差についてはRustが遅いんではなくて、JavaやPythonの連想配列が異常に速いことに起因しているようです。それにしてもJavaの速さは異常。。。そして何故に指定もしないのにマルチスレッド。。。

C++:そういえばC++知らないんでした。べたべたで作りました。もちろんシングルスレッドです。(リリースコンパイル実行です)

ちなみにC++で連想配列でなく、普通の配列では4億個は上限越えでNGでした。

(コンパイル上限近くの)要素数が2億6800万個では、データ準備で0.738sec 、データ集計で0.337sec でした。

今回は比較が連想配列縛りなので、悪しからず。。。ちなみに何をもって配列上限としているのかは不明でした。(追記ここまで)

Javaをサンマイクロシステムズ社が健在なころから、ずっと使ってます。何も指定しなければ&マルチスレッド系ライブラリを使用しなければ、主処理はシングルスレッド動作でしか動作しない認識でした。まぁ、最近のSpring/Web系のコンテナで動かすとマルチスレッドなこともありますが。。。今回は純粋なJava/main()です!

 

今回確認したJavaはJava17(OpenJDK 17.0.6 /2023-01-17)です。

ネットにも「勝手に」マルチスレッド動作する記事ないし。。。調査が必要です。

 

結論

Javaのベンチマークで、c++の2倍を切ってそこそこ早い記事を見て、昔からの遅いJavaを知っている身としては「眉唾だなぁ」と思ってました。

【Rust】Rust最速物語を他の言語と比較して検証してみたぴょい - Rのつく財団入り口 (hatenablog.com)

宇宙の晴れ上がり: プログラミング言語の実行速度比較(2023/5) (transparent-to-radiation.blogspot.com)

Java超絶早くなってるじゃん。ネイティブコンパイラに「そんなに」ひけをとらないってどういう頑張り??あと、JavaScript(Node.js)が超絶早いのは何故?

やっぱり、この世界は元いた世界と違うのか。。(個人的に昨年異世界転生の疑惑あり)

 

(蛇足)

あっこちゃん来もせず、用もないのに納豆売りがぁ~♪あ~あ~あぁナット~

 

(蛇足のソース)

Java

MainClass.java

package artifact;
import java.util.HashMap;
import java.util.Map;
public class MainClass {
final static int NEURO_COUT = 400000000;
public static void main(String[] args) {
System.out.println("Hello World!(Java)");
Runtime rt = Runtime.getRuntime();
// reportMemory(rt);
long startTime = System.currentTimeMillis();
Map<Integer, Neuron> nMap = new HashMap<Integer, Neuron>();
// データをNEURO_COUT件準備 JavaではclassをNewして準備
for (Integer i = 0; i < NEURO_COUT; i++) {
Neuron nr = new Neuron();
nr.setActivate(i);
nr.setBias(1.01);

nMap.put(i, nr);
}
long endTime = System.currentTimeMillis();

System.out.println(String.format("データ生成処理時間:%.3f sec" ,(endTime - startTime)/1000.0));

// キャッシュ等抑制のため開始を少し待つ(おまじない)
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
}
double sum = 0.0;

startTime = System.currentTimeMillis();

System.out.println("calc start");
// データを件数分加算
for (Integer i = 0; i < NEURO_COUT; i++) {
Neuron nr = nMap.get(i);
sum += nr.getActivate();
}
endTime = System.currentTimeMillis();
System.out.println("sum: " + sum);
System.out.println(String.format("データ集計処理時間:%.3f sec" , (endTime - startTime)/1000.0));
reportMemory(rt);
}
private static void reportMemory(Runtime rt) {
System.out.println("=== Memory ===");
System.out.println(String.format("total: %,d bytes",rt.totalMemory()));
System.out.println(String.format("free : %,d bytes" ,rt.freeMemory()));
System.out.println(String.format("max : %,d bytes" ,rt.maxMemory()));
}
}
 

 

Neuron.java (get/setはlombokを使ってます)

package artifact;
import lombok.Data;
  @Data
public class Neuron {
double bias;
double activate;
}

 

Python

# -*- coding: utf-8 -*-
import time
import psutil #Pypyではコメントアウトのこと

NEURO_COUNT = 200000000;

class Neuro():
def __init__(self,bias=1.0, activate=1.0):
self.bias = bias
self.activate = activate

def main():
print("Helo, world!(Python)")
# print(psutil.virtual_memory())
time_start = time.perf_counter()
hash_dict = {}
# データをNEURO_COUT件準備 Pythonではclassからインスタンス生成をして準備
for i in range(NEURO_COUNT):
hash_dict[i] = Neuro(bias=1.01, activate=float(i))
time_end = time.perf_counter()
print("データ生成処理時間: %.3f sec" % (time_end - time_start))

# キャッシュ等抑制のため開始を少し待つ(おまじない)
time.sleep(30)

sum: float = 0.0
print("calc start")
time_start = time.perf_counter()
# データを件数分加算
for i in range(NEURO_COUNT):
sum += hash_dict[i].activate
time_end = time.perf_counter()
print("sum: %e" % sum)
print("データ集計処理時間: %.3f sec" % (time_end - time_start))
print(psutil.virtual_memory())

if __name__ == "__main__":
main()

 

Rust

use std::collections::HashMap;
use std::time::{Duration, Instant};
use std::thread::sleep;
use sysinfo::{System, SystemExt};

const NEURO_COUT:i32 = 400_000_000;

struct Neuron {
    bias: f64,
    activate: f64,
}

fn main() {
    println!("Hello, world!(Rust)");
    // report_memory();

    let mut now = Instant::now();
    let mut n_map = HashMap::new();

    // データをNEURO_COUT件準備 JavaではclassをNewして準備
    for i in 0..NEURO_COUT {
        n_map.insert(i, Neuron{bias: 1.01 as f64, activate: i as f64});
    }
    let end_time = now.elapsed();
    println!("データ生成処理時間:{:?}", end_time);

    // キャッシュ等抑制のため開始を少し待つ(おまじない)
    sleep(Duration::from_millis(30000));

    let mut sum:f64 = 0.0;
    now = Instant::now();

    println!("calc start");
    // データを件数分加算
    for i in 0..NEURO_COUT {
        sum +=  n_map[&i].activate;
    }
    let end_time = now.elapsed();
    println!("sum:{:?}", sum);
    println!("データ集計処理時間:{:?}", end_time);
    report_memory();
}

fn report_memory() {
    let mut sys = System::new_all();
    sys.refresh_all();
    println!("=> system:");
    // RAM and swap information:
    println!("total memory: {} bytes", sys.total_memory());
    println!("used memory : {} bytes", sys.used_memory());
    println!("total swap  : {} bytes", sys.total_swap());
    println!("used swap   : {} bytes", sys.used_swap());
}
 

 

C++

#include <iostream>
#include <unordered_map>
#include <string>
#include <chrono>
#include <thread>
#include <windows.h> //OS依存
#include <psapi.h>
#include <time.h>

# define NEURO_COUNT 400000000

struct Neuron {
    double bias;
    double activate;
};

class ReportMem
{
public:
    void report_memory();
};

void ReportMem::report_memory()
{
    HANDLE hProc = GetCurrentProcess();
    PROCESS_MEMORY_COUNTERS_EX pmc;
    BOOL isSuccess = GetProcessMemoryInfo(
        hProc,
        (PROCESS_MEMORY_COUNTERS*)&pmc,
        sizeof(pmc));
    CloseHandle(hProc);

    std::cout << "Mem:PrivateUsage=" << pmc.PrivateUsage << std::endl;
}

int main()
{
    std::cout << "Hello World!(C++)\n";
    clock_t start_time = clock();
    std::unordered_map<std::string, Neuron> n_map;
    std::string str;
    for (int i = 0; i < NEURO_COUNT; i++) {
        Neuron n = { 1.01, i };
        str = std::to_string(i);
        n_map[str] = n;

    }
    clock_t end_time = clock();
    std::cout << "データ生成処理時間 = " << (double)(end_time - start_time) / CLOCKS_PER_SEC << "sec.\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(30000));    //30秒間スリープします
    start_time = clock();
    double sum = 0.0;
    std::cout << "calc start\n";
    for (int i = 0; i < NEURO_COUNT; i++) {
        str = std::to_string(i);
        sum += n_map[str].activate;
    }
    end_time = clock();
    std::cout << "sum = " << sum << "\n";
    std::cout << "データ集計処理時間 = " << (double)(end_time - start_time) / CLOCKS_PER_SEC << "sec.\n";
    ReportMem rm;
    rm.report_memory();
}