Cudaってどれくらい早いの?

 以前から気になって何度かプログラムを作ってはいたのですが、失敗を繰り返していました。AIに聞いたらあっけないくらい簡単に回答が出たので、載せておきます。
 ランダムな10桁の文字列から、先頭が”000000″(0が6個連続)になるハッシュ値(SHA-256)を見つけるまでのプログラムを作って速度を比較しました。

環境

OS windows11
CPU Intelcore i7 14700
GPU NVIDIA GeForce RTX 4060(8GB)

比較方法(準備)

①CPU版(Cuda不使用)

# python hash_256_CPU.py
import random
import string
import hashlib
import time

TARGET = "000000"
CHARS = string.ascii_letters + string.digits  # a-zA-Z0-9

def randstr(n=10):
    return ''.join(random.choices(CHARS, k=n))

count = 0
start_time = time.time()
last_time = start_time
last_count = 0

try:
    while True:
        s = randstr(10)
        h = hashlib.sha256(s.encode()).hexdigest()
        #count += 1

        if h.startswith(TARGET):
            elapsed = time.time() - start_time
            print("\n=== FOUND ===")
            print("STRING :", s)
            print("HASH   :", h)
            print("TIME   :", f"{elapsed:.4f} sec")
            #print("TRIES  :", f"{count:,}")
            break

except KeyboardInterrupt:
    print("\nInterrupted by Ctrl+C")

②GPU版(Cuda使用版)

# python hash_256_GPU.py
import cupy as cp
import time
import hashlib
import random

# ===== 設定 =====
ZEROS = 6                     
SHIFT = 32 - ZEROS * 4        # 上位何bitを見るか
TARGET = "0" * ZEROS

BLOCK = 256
GRID  = 65535
BATCH = BLOCK * GRID


# ===== base62文字 =====
table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

def base62(n):
    s = ""
    for _ in range(10):
        s = table[n % 62] + s
        n //= 62
    return s

# ================= CUDA KERNEL =================

kernel = r'''
__device__ __constant__ char table[63] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

extern "C" __global__
void brute(unsigned long long start, unsigned long long total,
           int shift, int *found, unsigned long long *result) {

    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= total || *found) return;

    unsigned long long n = start + idx;

    char s[10];
    for (int i = 9; i >= 0; i--) {
        s[i] = table[n % 62];
        n /= 62;
    }

    unsigned int h0 = 0x6a09e667;
    unsigned int h1 = 0xbb67ae85;
    unsigned int h2 = 0x3c6ef372;
    unsigned int h3 = 0xa54ff53a;
    unsigned int h4 = 0x510e527f;
    unsigned int h5 = 0x9b05688c;
    unsigned int h6 = 0x1f83d9ab;
    unsigned int h7 = 0x5be0cd19;

    unsigned char chunk[64] = {0};
    for(int i=0;i<10;i++) chunk[i] = s[i];
    chunk[10] = 0x80;
    chunk[63] = 80;

    unsigned int w[64];
    for(int i=0;i<16;i++){
        w[i] = (chunk[i*4]<<24)|(chunk[i*4+1]<<16)|(chunk[i*4+2]<<8)|(chunk[i*4+3]);
    }

    for(int i=16;i<64;i++){
        unsigned int s0 = __funnelshift_r(w[i-15], w[i-15], 7) ^
                          __funnelshift_r(w[i-15], w[i-15], 18) ^ (w[i-15] >> 3);
        unsigned int s1 = __funnelshift_r(w[i-2], w[i-2], 17) ^
                          __funnelshift_r(w[i-2], w[i-2], 19) ^ (w[i-2] >> 10);
        w[i] = w[i-16] + s0 + w[i-7] + s1;
    }

    unsigned int a=h0,b=h1,c=h2,d=h3,e=h4,f=h5,g=h6,h=h7;

    unsigned int k[64]={
        0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
        0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
        0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
        0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
        0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
        0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
        0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
        0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
    };

    for(int i=0;i<64;i++){
        unsigned int S1 = __funnelshift_r(e,e,6)^__funnelshift_r(e,e,11)^__funnelshift_r(e,e,25);
        unsigned int ch = (e & f) ^ ((~e) & g);
        unsigned int temp1 = h + S1 + ch + k[i] + w[i];
        unsigned int S0 = __funnelshift_r(a,a,2)^__funnelshift_r(a,a,13)^__funnelshift_r(a,a,22);
        unsigned int maj = (a & b) ^ (a & c) ^ (b & c);
        unsigned int temp2 = S0 + maj;

        h=g; g=f; f=e; e=d+temp1; d=c; c=b; b=a; a=temp1+temp2;
    }

    h0 += a;

    if((h0 >> shift) == 0){
        *found = 1;
        *result = start + idx;
    }
}
'''

# ================= Python 側 =================

mod = cp.RawModule(code=kernel)
func = mod.get_function("brute")

found  = cp.zeros(1, dtype=cp.int32)
result = cp.zeros(1, dtype=cp.uint64)

start = random.randrange(62**10)
count = 0
t0 = time.time()

try:
    while True:
        found.fill(0)
        func((GRID,), (BLOCK,), (start, BATCH, SHIFT, found, result))
        cp.cuda.runtime.deviceSynchronize()

        count += BATCH

        if int(found.get()[0]):
            num = int(result.get()[0])
            s = base62(num)
            h = hashlib.sha256(s.encode()).hexdigest()

            if h.startswith(TARGET):
                print("\n=== FOUND ===")
                #print("NUMBER:", num)
                print("STRING:", s)
                print("HASH  :", h)
                #print("TRIES :", f"{count:,}")
                print("TIME  :", f"{time.time()-t0:.8f} sec")
                break
        start += BATCH 

except KeyboardInterrupt:
    print("\nInterrupted by Ctrl+C")

※Cudaを使用するための準備

# Cudaのバージョンを確認
c:\ > nvidia-smi
Wed Feb 11 11:37:27 2026
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 576.28                 Driver Version: 576.28         CUDA Version: 12.9     |
|-----------------------------------------+------------------------+----------------------+

# 私の環境だと  CUDA Version: 12.9 

# Cuda用のpythonライブラリをインストール
c:\ > pip install cupy-cuda12x numpy   # cupy-cuda12x 12系のライブラリ

比較

① CPU版を実行した様子(1回目)

# CPU版実行結果
c:\ > python hash_256_CPU.py

=== FOUND ===
STRING : MFipUySTvU
HASH   : 00000090d7a0b1299cbe218e63a82d0dc74ee788436936f4b3c0c3f3ee0bc6c5
TIME   : 6.7857 sec

② GPU版の実行結果(1回目)

# GPU版実行結果 ZEROS = 6
c:\ > python hash_256_GPU.py

=== FOUND ===
STRING: 0Y61DC4953
HASH  : 00000097a57f46641bf79a146cd1c0c972842cf6bd86f77ceee45cf2079d7df2
TIME  : 0.00095725 sec

 ぱっと見ただけで、GPUの方が圧倒的に早いことがわかります。

③ 一覧による比較

 それぞれのプログラムを10回ずつ実行して、平均値を求め比較したところ、
   CPU 15.23284秒
   GPU 0.009985256秒
という結果になりました。
 このプログラムで比較したところ、私の環境では、GPU版の方がCPU版より
   約1525倍速い(15.23284/0.009985256)
という結果になりました。ヤッタネ!
 

CPU発見までの時間文字列HASH値(SHA-256)
16.7857MFipUySTvU00000090d7a0b1299cbe218e63a82d0dc74ee788436936f4b3c0c3f3ee0bc6c5
23.8864jvsiieLXis00000017f324ffe29452ffb20d9c443f73049d709cef90b5f0c58558a21530b7
37.4533hMaOzHEE23000000911e09a5e1b9dece956331f85ed8b645c822a876ad2e72c2630fdabdea
44.9091OhIsWYkPX000000b6b0a24dd38ee3e8709c4c88794162141b43d3b87a7ea40daa707cc4db
562.2687f2SfpoW7o2000000e21ed7a9a3327797138203cf06e0d7c4385dcdac71a140aab8bd2a367f
64.1982tzwY5mbKZM00000068f3caf3f06c14a97fe23bfc880866c70a0860b2b2ffa1805e846fe401
717.7354huZQrP927x00000001730353ea98d75936f8d427eb690bdebce53aa505ead45693df5497d8
818.2202Xh4d6gxEqR000000690079a1713fb6363f7afa0e370ca592eb8fa69037f3d46b722db954a6
910.225359stXEBykl000000ac4d46e2593127bbbf28f946ed245b7f8247dc04f91e3c7786edc8ad30
1016.6462piYYES4b4J000000d250133a42ca5d27f5f158b56320c5c9cb0f2714cd5c80cfb427a92705
AVE15.23284
GPU発見までの時間文字列HASH値(SHA-256)
10.000957250Y61DC495300000097a57f46641bf79a146cd1c0c972842cf6bd86f77ceee45cf2079d7df2
20.01149106UZ6Yv0bE0i0000006a367eaa33a313cfb471806126cc67c34da1e7915bbd345c915add7882
30.00199962jyOiugp1M700000008ca2544733bf163d7f0301269008a6b8c9828793033e8c728a565715a
40.010003577rVRnnxT8O0000001454d35d8d6874a27de444171d61c5b682aa6787138b41467bee68c936
50.03151608FkkVRNebes0000002d365a7b1bebd02c5b5a113307c131355e092e7960fa2dd753a15b1173
60.00400233KgEnsu3UT10000007e02e5f8537554638af32ab82e4ba391bd9ebb598e29ec5b550d6ba950
70.0206964nJIpEdH0Uh00000090fc18d072ef457370adf886af560827d1c0da455f6367f1c6f9893494
80.004107zZfrXNRuLR0000009d9b26bfbfdc15f27d7aa561f053b6e17788349759d370d0073dbdd8fe
90.00499701SyiSUDcQqI0000000bddaeb0a71a2d6b92f67856a5160693f86c44f7308d93d6525b14118e
100.01008224FTZVLCIwGy00000022a5ef411410c1644cb5dd2f0a7c157d3ff8f618f7f4056d4f28c1b58a
AVE0.009985256

④ 更に0の桁数が大きいHASH値の検索
  7桁が”0″になる文字列検索を行ったところ、次のような結果になりました。

# python ZEROS = 7
c:\ > python hash_256_CPU.py
=== FOUND ===
STRING: eLJz8Piawt
HASH  : 0000000ee86c1a3f9d329533089ca7876998d7c0d0745d6eeaef622e95115841
TIME  : 0.02571392 sec

  8桁が”0″になる文字列検索を行ったところ、次のような結果になりました。

# python ZEROS = 8
c:\ > python hash_256_GPU.py

=== FOUND ===
STRING: fDbwxZsc81
HASH  : 000000009125b14edbe6fd9ca785a04306c53e0d6f3f73e95dad8ef053e2091f
TIME  : 2.03842711 sec

  この調子で9桁目を計算しようとしましたが、答えが返ってきません。

問題点 ~ 0が9つ以上並ぶHASHの検索ができない

 これまでの方法だと
   0が8つ並んだHASH値は検索できる
ものの、
   0が9つ以上並んだHASH値は検索できない
ことがわかりました。ただ、単に時間がかかっているだけなのかもしれませんが、入力文字数10桁の中から先頭が9桁0になるHASH値を探すのは現実的ではないのかもしれません。(そもそも存在しない可能性もある)
 そこで、入力文字列をさらに上げることにしました。

 実装したところ、11桁以上のランダム文字列を生成すること自体は容易なのですが、その探索開始位置を示す start 変数が極端に大きくなるという問題に突き当りました。
 今回の実装では、
   start = random.randrange(63**N)
のようにして検索開始位置を CUDA に渡していました。
 しかし、入力文字が11桁になるとstartが
   63**10 = 984,930,292,000,000,000 ≒ 9.8×10^17
となり、Cuda 側で扱える整数サイズを超過し、オーバーフローになってしまうのです。
 Python 側では int に上限はないとのことですが、CUDA カーネル内部は Cの整数型(long long等) に変換されるため、サイズ超過したことが原因だと思われます。

解決策

 この問題を解決するために、入力文字列を2つに分け、それぞれをカウントする方法に変更しました。
   入力文字列 = 入力文字列1 + 入力文字列2
 Cudaに渡す変数は増えますが、理論的にはさらに大きな文字列を渡してカウントすることが可能であり、この方法を使えば事実上無制限に文字列をカウントアップすることが可能です。

作成したHash値検索プログラム

import cupy as cp
import time
import hashlib
import random

# ===== 設定 =====
ZEROS = 9
TARGET = "0" * ZEROS

BLOCK = 256
GRID  = 65535

# ===== base62 =====
table = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

def base62_8(n):
    s = ""
    for _ in range(8):
        s = table[n % 62] + s
        n //= 62
    return s

# ================= CUDA KERNEL =================

kernel = r'''
__device__ __constant__ char table[63] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

__device__ __constant__ unsigned int k[64]={
    0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
    0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
    0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
    0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
    0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
    0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
    0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
    0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};

extern "C" __global__
void brute(unsigned long long start1,
           unsigned long long start2,
           int *found,
           unsigned long long *r1,
           unsigned long long *r2) {

    unsigned int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if (*found) return;

    unsigned long long n1 = start1 + tid;
    unsigned long long n2 = start2 + blockIdx.x;

    char s[16];

    #pragma unroll
    for (int i = 7; i >= 0; i--) {
        s[i] = table[n1 % 62];
        n1 /= 62;
    }
    #pragma unroll
    for (int i = 15; i >= 8; i--) {
        s[i] = table[n2 % 62];
        n2 /= 62;
    }

    unsigned int h0 = 0x6a09e667;
    unsigned int h1 = 0xbb67ae85;
    unsigned int h2 = 0x3c6ef372;
    unsigned int h3 = 0xa54ff53a;
    unsigned int h4 = 0x510e527f;
    unsigned int h5 = 0x9b05688c;
    unsigned int h6 = 0x1f83d9ab;
    unsigned int h7 = 0x5be0cd19;

    unsigned char chunk[64] = {0};
    #pragma unroll
    for(int i=0;i<16;i++) chunk[i] = s[i];
    chunk[16] = 0x80;
    chunk[63] = 128;

    unsigned int w[64];

    #pragma unroll
    for(int i=0;i<16;i++){
        w[i] = (chunk[i*4]<<24)|(chunk[i*4+1]<<16)|(chunk[i*4+2]<<8)|(chunk[i*4+3]);
    }

    #pragma unroll
    for(int i=16;i<64;i++){
        unsigned int s0 = __funnelshift_r(w[i-15], w[i-15], 7) ^
                          __funnelshift_r(w[i-15], w[i-15], 18) ^ (w[i-15] >> 3);
        unsigned int s1 = __funnelshift_r(w[i-2], w[i-2], 17) ^
                          __funnelshift_r(w[i-2], w[i-2], 19) ^ (w[i-2] >> 10);
        w[i] = w[i-16] + s0 + w[i-7] + s1;
    }

    unsigned int a=h0,b=h1,c=h2,d=h3,e=h4,f=h5,g=h6,h=h7;

    #pragma unroll
    for(int i=0;i<64;i++){
        unsigned int S1 = __funnelshift_r(e,e,6)^__funnelshift_r(e,e,11)^__funnelshift_r(e,e,25);
        unsigned int ch = (e & f) ^ ((~e) & g);
        unsigned int temp1 = h + S1 + ch + k[i] + w[i];
        unsigned int S0 = __funnelshift_r(a,a,2)^__funnelshift_r(a,a,13)^__funnelshift_r(a,a,22);
        unsigned int maj = (a & b) ^ (a & c) ^ (b & c);
        unsigned int temp2 = S0 + maj;

        h=g; g=f; f=e; e=d+temp1; d=c; c=b; b=a; a=temp1+temp2;
    }

    h0 += a;
    h1 += b;

    if(h0 == 0 && (h1 >> 28) == 0){
        if(atomicCAS(found, 0, 1) == 0){
            *r1 = start1 + tid;
            *r2 = start2 + blockIdx.x;
        }
    }
}
'''

# ================= Python 側 =================

mod = cp.RawModule(code=kernel)
func = mod.get_function("brute")

found = cp.zeros(1, dtype=cp.int32)
r1 = cp.zeros(1, dtype=cp.uint64)
r2 = cp.zeros(1, dtype=cp.uint64)

start1 = random.randrange(62**8)
start2 = random.randrange(62**8)

t0 = time.time()

try:
    while True:
        found.fill(0)

        func((GRID,), (BLOCK,), (start1, start2, found, r1, r2))
        cp.cuda.runtime.deviceSynchronize()

        if int(found.get()[0]):
            n1 = int(r1.get()[0])
            n2 = int(r2.get()[0])

            s = base62_8(n1) + base62_8(n2)
            h = hashlib.sha256(s.encode()).hexdigest()

            if h.startswith(TARGET):
                print("\n=== FOUND ===")
                print("STRING:", s)
                print("HASH  :", h)
                print("TIME  :", f"{time.time()-t0:.3f} sec")
                break

        start1 += GRID * BLOCK
        if start1 >= 62**8:
            start1 = 0
            start2 += GRID

except KeyboardInterrupt:
    print("\nInterrupted")

更にHASH値の検索 ~10桁まで

 このプログラムを使って、0が9個並ぶHASH値の検索は、10回平均値をとったところ、
   28.53秒
でした。

# python ZEROS = 9
c:\ > python hash_256_GPU_30.py

=== FOUND ===
STRING: XFOX2Rz6YrrJ0YIT
HASH  : 000000000e17df1847867773195ce216d7bbcf469129d3ed66d9c494a3dc1c02
TIME  : 19.627 sec

 続いて0が10個並ぶHASH値を検索したところ、

>python hash_256_GPU_30.py

=== FOUND ===
STRING: sP2kuyFP6HDUWzS7
HASH  : 00000000008a622b53cecd8ecf47f0194f55034124af739d81963a5168555e95
TIME  : 742.806 sec

でした。(三回の平均値は376秒でした)

予想 ~マイニングが成功するまでの道のり

 ここまでの結果をもとに、ビットコインマイニングに必要な時間を計算しました。
 nが1増えると検索時間がおおむね10倍になっていると仮定すると、ビットコインのマイニングに必要なN=19のハッシュ値計算に必要な時間は、
  1万2先年
となりました。
 かなり強引な計算ですので全くあてにはできませんが、マイニングがどれほど大変なのかがわかりました。

n(ハッシュ値の0が続く回数)検索時間(秒)備考(約)
928.5368
10376.7276.27分
114000(予想)66.66分
1240000(予想)11時間
13400000(予想)4.62日
144000000(予想)46.29日
1540000000(予想)1.26年
16400000000(予想)12.68年
174000000000(予想)126.83年
1840000000000(予想)1268.39年
19400000000000(予想)12683.91年
赤字部分は予想

結果

 今回はCPUとGPUの速度比較から始まって脱線し、ビットコインマイニングに必要な0が19個並ぶHASH値の検索に挑みましたが、全く歯が立たないことがわかりました。
 それにしてもCuda早いですね。プログラムが複雑になるので、私の頭ではプログラムを組めませんが、AIに手伝ってもらえば、なんとかなりそうな印象です。(ほぼお任せですが)