
お知らせ
この記事は2026年10月10日にリライトしました。
「つくって学ぶ」に合わせて、コマンドとC言語のプログラムでCPUの性能指標を実際に確かめる内容に改めています。
コンピュータの演算と制御は、CPU(Central Processing Unit:中央処理装置)という装置が担っている。
CPUの性能を理解するためのポイントとして、クロック周波数、コア、レジスタのデータサイズの3つを見ていこう。
- クロック周波数
- コア
- レジスタのデータサイズ
クロック周波数
CPUが動く速さを表す指標である。コンピュータは、内部にクロック発振器を持っていて、CPUはこの発振器が発生させるテンポに合わせて命令を処理する。
クロック周波数は、この発振器が1秒間に発生させる信号の回数である。クロック周波数の単位は「Hz(ヘルツ)」である。1秒間に1回の信号を発生させることを1Hzという。
例えば、3GHz(ギガヘルツ)なら「3×10億=30億回/秒」となる。ギガは10億という意味である。
コンピュータでは、CPUの処理を「クロック」で表す。「クロック」は、発振器がテンポを刻むことである。発振器が1回分のテンポを刻むことを1クロックという。
より多くの処理を、より速く実行するには、より速いテンポに対応できるCPUが必要となる。
ただし、クロック周波数が高ければ必ず速いというわけではない。1クロックでできる処理の量や、後述するコアの数によっても、実際の速さは変わる。
(クロック周波数)

↑1秒間に4つのクロックがあるので、クロック周波数は4Hz
コア
コアとは、CPU内にある「演算処理を行う装置」である。数年前までは、1つのCPUに1つのコアであった。現在は、1つのCPUに複数のコアが搭載されていることがほとんどである。
このようなCPUを「マルチコアプロセッサ」という。
2つのコアを持つプロセッサを「デュアルコアプロセッサ」といい、4つのコアを持つプロセッサを「クワッドコアプロセッサ」という。
コアが複数あるCPUでは、複数の処理を並行して実行できる。そのため、処理を分担できるプログラムでは、高速化が期待できる。ただし、コア数が多ければ、必ず性能が高くなるというわけではない。
レジスタのデータサイズ
レジスタとは、CPU内にある「データを一時的に記憶する装置」である。
コンピュータは、主に主記憶装置(メモリ)にデータを保存するが、CPUの内部にもデータを保存する場所としてレジスタが用意されている。
レジスタのデータサイズは主に「32ビット」と「64ビット」の2種類である。
32ビットのデータサイズのレジスタを持つCPUを「32ビットCPU」といい、64ビットのデータサイズのレジスタを持つCPUを「64ビットCPU」という。
64ビットCPUは、一度に扱えるデータが大きい。また、扱えるメモリの範囲も広い。32ビットでは最大約4GBだが、64ビットではそれよりはるかに大きなメモリを扱える。
なお、主記憶装置などの記憶装置については、次の記事で解説している。
手元のパソコンで確かめてみよう
まずは、自分の環境のCPUがどうなっているのか、コマンドで確認してみよう。
GitHubのCodespacesや、LinuxまたはWSLのターミナルで実行できる。 (GitHubについては、「GitHubとは?~ プログラミング学習を「つくって学ぶ」ための土台~」)を参照してほしい。)
| コマンド | わかること |
|---|---|
lscpu | CPUの型番、クロック周波数、コア数など |
nproc | 使えるコアの数 |
getconf LONG_BIT | 64ビットか32ビットか |
lscpu
nproc
getconf LONG_BIT表示される内容は環境によって異なる。実行して、次のことを見つけてみよう。
- CPUの型番と、クロック周波数(MHz、GHz)
- コアの数
- 64ビットか、32ビットか
なお、環境によっては、表示されるコア数と、実際に使えるコア数が異なることがある。
C言語でつくって確かめてみよう
次に、CPUの性能指標のうち「レジスタのデータサイズ」と「コア」を、プログラムで確かめる。
① ポインタのサイズから実行環境を確かめる(bits.c)
C言語では、sizeof で型やポインタのサイズ(バイト数)を調べられる。 ポインタは、メモリ上の場所(アドレス)を表す値である。64ビットの環境では、このアドレスを8バイト(64ビット)で表す。
#include <stdio.h>
int main(void) {
printf("char : %zu バイト\n", sizeof(char));
printf("int : %zu バイト\n", sizeof(int));
printf("long : %zu バイト\n", sizeof(long));
printf("long long : %zu バイト\n", sizeof(long long));
printf("ポインタ : %zu バイト(%zu ビット)\n",
sizeof(void *), sizeof(void *) * 8);
return 0;
}gcc -O2 -o bits bits.c
./bits実行結果
char : 1 バイト
int : 4 バイト
long : 8 バイト
long long : 8 バイト
ポインタ : 8 バイト(64 ビット)
ポインタが8バイト(64ビット)と表示されれば、このプログラムは64ビットのポインタを使う実行環境で動作していることがわかる。
なお、long のサイズは、OSやコンパイラによって異なる。
② コアの数で、速くなるかを確かめる(cores.c)
コアが複数あると、それぞれのコアが別々の処理を同時に実行できる。 そこで、同じ量の計算(8億回)を、次の2つの方法で実行して、かかった時間を比べる。
- 1つのスレッドで、すべての計算を行う
- コアの数だけスレッドに分けて、同時に計算を行う
「スレッド」とは、プログラムの中で処理を実行する流れのことである。スレッドを複数作ると、OSがそれぞれを別々のコアに割り当てて、同時に実行してくれる。
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#define TOTAL_WORK 800000000ULL /* 全体の計算回数(8億回) */
#define MAX_THREADS 64
typedef struct {
uint64_t count; /* このスレッドが行う計算回数 */
uint64_t result; /* 計算結果(計算が省略されないように使う) */
} Task;
/* 実時間(秒)を返す */
static double now_sec(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1e9;
}
/* 単純な計算をcount回繰り返す(メモリにはほとんど触らない) */
static void *worker(void *arg) {
Task *t = (Task *)arg;
uint64_t x = 1;
uint64_t sum = 0;
for (uint64_t i = 0; i < t->count; i++) {
x = x * 6364136223846793005ULL + 1442695040888963407ULL;
sum += x >> 33;
}
t->result = sum;
return NULL;
}
/* 全体の計算をn_threads個のスレッドに分けて実行し、かかった時間を返す */
static double run(int n_threads, uint64_t *check) {
pthread_t th[MAX_THREADS];
Task task[MAX_THREADS];
double start = now_sec();
for (int i = 0; i < n_threads; i++) {
task[i].count = TOTAL_WORK / n_threads;
if ((uint64_t)i < TOTAL_WORK % (uint64_t)n_threads) {
task[i].count++; /* 割り切れない分は、先頭のスレッドに1回ずつ追加する */
}
task[i].result = 0;
pthread_create(&th[i], NULL, worker, &task[i]);
}
for (int i = 0; i < n_threads; i++) {
pthread_join(th[i], NULL);
}
double elapsed = now_sec() - start;
*check = 0;
for (int i = 0; i < n_threads; i++) {
*check += task[i].result;
}
return elapsed;
}
int main(int argc, char *argv[]) {
long cores = sysconf(_SC_NPROCESSORS_ONLN); /* 使えるコアの数 */
if (argc > 1) cores = atol(argv[1]); /* 引数があれば、その数を使う */
if (cores < 1) cores = 1;
if (cores > MAX_THREADS) cores = MAX_THREADS;
printf("比較に使うスレッド数: %ld\n", cores);
printf("全体の計算回数: %llu 回\n\n", (unsigned long long)TOTAL_WORK);
uint64_t check;
double t1 = run(1, &check);
printf("スレッド数 1: %6.3f 秒\n", t1);
/* コア数が2以上なら、コア数のスレッドに分けて実行する */
if (cores >= 2) {
double tn = run((int)cores, &check);
printf("スレッド数 %ld: %6.3f 秒\n", cores, tn);
printf("\n速くなった倍率: %.2f 倍\n", t1 / tn);
} else {
printf("コアが1つなので、比較はできません。\n");
}
printf("(確認用: %llu)\n", (unsigned long long)check);
return 0;
}スレッドを使うので、-pthread を付けてコンパイルする。
gcc -O2 -pthread -o cores cores.c
./coresスレッド数は、引数で指定することもできる。
./cores 2
実行結果
比較に使うスレッド数: 2
全体の計算回数: 800000000 回
スレッド数 1: 1.004 秒
スレッド数 2: 0.654 秒
速くなった倍率: 1.54 倍
(確認用: 858993694230198354)
※結果は環境によって大きく変わる。ご自分の環境で実行して確かめてほしい。
プログラムのポイント
計算を均等に分ける
全体の計算回数(8億回)を、スレッド数で割って各スレッドに割り当てている。割り切れない場合は、余りの分を先頭のスレッドに1回ずつ追加して、合計がぴったり8億回になるようにしている。 どちらの場合も、行う計算の総量は同じである。違うのは、それを何人で分担するかだけである。
倍率はコア数にほぼ近づく
コアが複数ある環境では、複数のスレッドに分けたほうが、時間が短くなる。 ただし、倍率がコア数とぴったり同じになることは少ない。OSの処理や、スレッドの起動にも時間がかかるためである。
コアが足りないと、速くならない
コアが1つしかない環境で、スレッド数だけを増やしても、速くはならない。 スレッドは同時に動かせる数がコアの数で決まるため、実際には順番に実行されるだけになるからである。引数でスレッド数をコア数より大きくして実行すると、確かめられる。
時間の測り方
複数のスレッドで動かすと、CPUの処理時間は各スレッドの合計になる。実際に経過した時間を測るために、clock() ではなく clock_gettime(CLOCK_MONOTONIC, ...) を使っている。
まとめ
- CPUの性能は、クロック周波数・コア・レジスタのデータサイズの3つの指標で表す。
- クロック周波数が高いほど速いが、それだけで実際の速さが決まるわけではない。
- コアが複数あるCPUは、処理を分担して同時に実行できる。ただし、コア数を超えてスレッドを増やしても速くならない。
sizeof(void *)を調べると、64ビットか32ビットかを確認できる。
(参考)筆者のリポジトリ
この記事で扱ったコードは、以下のGitHubリポジトリの cpu フォルダで公開している。

更新履歴
- 2023.06.29 公開
- 2026.10.10 「つくって学ぶ」に合わせてリライト(手元での確認方法とC言語のプログラムを追加、「発信器」を「発振器」に修正、括弧の誤りを修正、64ビットCPUの説明を補足)




コメント