コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2023年11月6日 >>> 2023年10月24日
link もっと後

2023年11月6日

yesの高速化(パイプ限定)

目次: ベンチマーク

FizzBuzzを作っていて気づきましたが、vmsplice()を使うとメチャクチャ速いyesコマンドを実装できます。

昔に紹介した通り(2017年6月14日の日記参照)GNU yes 8.26近辺から出力がとても速くなっています。あとで分析しますがwrite()を使ったときの最速と思われる速度が出ますが、出力先をパイプに限定して良ければvmsplice()を使うことでさらに速くできます。

vmsplice()で端末に出力するとEBADFエラーになる
$ ./yes

vmsplice: Bad file descriptor

ちなみに今回紹介する高速化の手法であるvmsplice()はパイプ以外、例えば端末に出そうとするとエラーになりますから、汎用的なyesコマンドの実装としては使えません。状況に制限を掛けてまで高速なyesが欲しい場合が果たしてあるだろうか?と言われると、うーん、すぐには思いつかないですね……。ベンチマークには役に立ちますけども。

レギュレーションとテスト

本来のyesコマンドの仕様は「引数で受け取った文字列と改行を無限に出力する」ですが、ベンチマークの都合上どこか一定の場所で終わってほしいので、適当に0x2ffffffff行(128億行)くらい出力したら終わりとします。行数は多少増減しても気にしないことにします。デフォルトでは1行2バイト('y'と改行)なので出力するデータ量の合計は24GBくらいになります。

内容的には難しくないので不要な気もしますが、正常動作を確かめるテストプログラムを作ります。基本的に延々と同じ内容の行が出力されるだけなので、全て見なくても0x0fff_ffff(2億行)も見れば十分でしょう。たぶん。

yesのテストプログラム

// 20231106_test_yes.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
	const char *ex;
	char expected[256];
	char *inbufp = NULL;
	size_t insz = 0;

	if (argc < 2) {
		ex = "y";
	} else {
		ex = argv[1];
	}

	snprintf(expected, sizeof(expected), "%s\n", ex);

	for (unsigned int i = 1; i < 0xfffffff; i++) {
		getline(&inbufp, &insz, stdin);

		if ((i & 0xffffff) == 0) {
			printf("\r%u      ", i);
			fflush(stdout);
		}
		if (strcmp(expected, inbufp) != 0) {
			printf("\n");
			printf("Not matched in %u\n", i);
			printf("  expected: %s\n", expected);
			printf("  input   : %s\n", inbufp);
			return 1;
		}
	}

	printf("\nOK\n");

	return 0;
}

本物のyesをテストしてみてfailしないか確かめます。

テストのテスト

$ yes | ./test_yes
251658240
OK


$ yes aaaaaaaa | ./test_yes aaaaaaaa
251658240
OK


### 引数の指定が効いているか確かめる

$ yes | ./test_yes aaaaaaaa

Not matched in 1
  expected: aaaaaaaa

  input   : y


$ yes aaaaaaaa | ./test_yes

Not matched in 1
  expected: y

  input   : aaaaaaaa

良さそうですね。あとは測定環境です。省電力PCの測定環境は、

  • Intel Pentium J4205/1.5GHz
  • DDR3L-1600 8GB x 2
  • Linux kernel 6.1.52
  • GCC 12.2.0 (Debian 12.2.0-14)
  • glibc 2.36 (Debian 2.36-9+deb12u1)

デスクトップPCの測定環境は、

  • AMD Ryzen 7 5700X
  • DDR4-3200 32GB x 2
  • Linux kernel 6.4.13 (Debian 6.4.13-1)
  • GCC 13.2.0 (Debian 12.2.0-14)
  • glibc 2.37 (Debian 2.37-7)

準備完了です。ではいってみよう。

単純なyes

最初はprintf()で普通に実装しましょう。

単純なyes

// 20231106_yes_simple.c

#include <stdint.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
	const char *arg;

	if (argc < 2) {
		arg = "y";
	} else {
		arg = argv[1];
	}

	for (uint64_t i = 0; i < 0x2ffffffff; i++) {
		printf("%s\n", arg);
	}

	return 0;
}
単純なyesの速度
$ gcc 20231106_yes_simple.c -msse4 -O3

24.0GiB 0:08:31 [48.1MiB/s] [           <=>                                    ]

real    8m31.031s
user    8m26.537s
sys     0m36.512s

一度のprintf()で2バイトしか出力しないので、メチャクチャ遅いですね。

バッファリング版yes

次はwrite()とバッファリングを使います。アイデアは単純で、適当な大きさのバッファに出力する文字を詰められるだけ詰めて、バッファをwrite()に渡して複数行を一気に出力する方法です。本来の処理では不要ですが、ベンチマークのため一度に何行出力しているか覚えておく必要があります。

バッファリング版yes

// 20231106_yes_buf.c

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define CHUNKSIZE    (4096 * 2)

char output[CHUNKSIZE] __attribute__((aligned(4096)));

int main(int argc, char *argv[])
{
	const char *arg;
	char out_one[256];
	size_t len_one, len = 0;
	int d = 0;

	if (argc < 2) {
		arg = "y";
	} else {
		arg = argv[1];
	}

	len_one = snprintf(out_one, sizeof(out_one), "%s\n", arg);
	while (len + len_one < sizeof(output) - 1) {
		strcat(output, out_one);
		len += len_one;
		d++;
	}

	for (uint64_t i = 0; i < 0x2ffffffff - 1; i += d) {
		write(1, output, len);
	}

	return 0;
}
バッファリング版yesの速度
$ gcc 20231106_yes_buf.c -msse4 -O3

24.0GiB 0:00:11 [2.16GiB/s] [           <=>                                    ]

real    0m11.095s
user    0m1.564s
sys     0m20.571s


(参考 GNU yesの速度)

$ yes --version

yes (GNU coreutils) 9.1
(以下略)


$ time taskset 0x1 yes | taskset 0x4 pv > /dev/null

24.2GiB 0:00:11 [2.23GiB/s] [          <=>                                     ]
^C

real    0m11.600s
user    0m1.528s
sys     0m21.621s

一気に速くなりました。GNU yesの速度も参考に載せましたが、ほぼ同じ速度です。これがwrite()で出力するときの限界速度でしょう。

vmsplice版yes

最後はvmsplice()です。基本的なアイデアはバッファリング版yesと同じです。ただしvmsplice()に対応するために、ダブルバッファリングとバッファ終端からはみ出た場合の処理を追加します。

vmsplice版yes

// 20231106_yes_vmsplice.c

#define _GNU_SOURCE

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>
#include <sys/uio.h>

#define CHUNKSIZE    (4096 * 64)

char buf2[2][CHUNKSIZE + 4096] __attribute__((aligned(4096)));
int f __attribute__((aligned(8)));
char output[2048] __attribute__((aligned(4096)));

static void vwrite(int fd, void *buf, size_t count)
{
	struct iovec iov;
	ssize_t n;

	iov.iov_base = buf;
	iov.iov_len = count;

	while (iov.iov_len > 0) {
		n = vmsplice(1, &iov, 1, 0);
		if (n < 0) {
			perror("vmsplice");
			exit(1);
		}
		iov.iov_base += n;
		iov.iov_len -= n;
	}
}

int main(int argc, char *argv[])
{
	const char *arg;
	char out_one[256];
	char *p = buf2[f];
	size_t len_one, len = 0;
	int d = 0;

	fcntl(1, F_SETPIPE_SZ, CHUNKSIZE);

	if (argc < 2) {
		arg = "y";
	} else {
		arg = argv[1];
	}

	len_one = snprintf(out_one, sizeof(out_one), "%s\n", arg);
	while (len + len_one < sizeof(output) - 1) {
		strcat(output, out_one);
		len += len_one;
		d++;
	}

	for (uint64_t i = 0; i < 0x2ffffffff - 1; i += d) {
		memcpy(p, output, len);
		p += len;

		int n = p - buf2[f] - CHUNKSIZE;
		if (n >= 0) {
			vwrite(1, buf2[f], CHUNKSIZE);
			f = !f;
			memcpy(buf2[f], &buf2[!f][CHUNKSIZE], n);
			p = &buf2[f][n];
		}
	}

	return 0;
}
vmsplice版yesの速度
$ gcc 20231106_yes_vmsplice.c -msse4 -O3
24.0GiB 0:00:03 [7.14GiB/s] [   <=>                                            ]

real    0m3.367s
user    0m1.849s
sys     0m3.297s

予想はしていましたがメチャクチャ速くなりました……。vmsplice()恐るべし。

Ryzen 7での測定結果
$ gcc 20231106_yes_simple.c -msse4 -O3

24.0GiB 0:01:54 [ 213MiB/s] [              <=>                                 ]

real    1m54.938s
user    1m52.312s
sys     0m22.559s


$ gcc 20231106_yes_buf.c -msse4 -O3

24.0GiB 0:00:05 [4.49GiB/s] [     <=>                                          ]

real    0m5.347s
user    0m0.912s
sys     0m9.776s


$ gcc 20231106_yes_vmsplice.c -msse4 -O3

24.0GiB 0:00:00 [33.7GiB/s] [<=>                                               ]

real    0m0.715s
user    0m0.508s
sys     0m0.721s

PentiumとRyzen 7の測定結果、どの程度速くなったかを合わせて表にすると、

FizzBuzzの種類Pentium, GCC -O3倍率Ryzen 7, GCC -O3倍率
単純 511.031- 114.938-
バッファリング11.600 x44.0 5.347 x21.5
vmsplice 3.367 x151.80.715 x160.7

使える場所が限定されるとはいえ素晴らしい効き目ですね。ちなみにRyzen 7はバッファサイズを4倍(1MB x 2)にするとさらに速くなって、0.612秒(39.4GiB/s、187.8倍)くらいになります。まさか1秒も掛からないとは思わなんだ……。

ソースコード

ソースコードはこちらからどうぞ。

編集者:すずき(2023/11/07 03:09)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2023年11月3日

Googleから探しやすくしたい

この日記にはいくつかヘッダ(Hxタグ)を使っており、文書の構造は下記のようにしています。

  • H1: タイトル
  • H2: 不使用(昔は使っていたけどデザイン変更でなくなった)
  • H3: 日記の日付
  • H4: 日記内のトピック

しかしどうもGoogleさんはH4タグを拾わないときがあるようで、


H4タグの内容を拾うときと拾わないときがある?

こんな風に日付だけが出て、内容が良くわからなくなってしまうことがあります。試しに日記内のトピックを格上げし、日記の日付と同格のH3にして検索結果がどうなるか観察しようと思います。

日付を消すことも少し考えましたが、とりあえず今のままにしておきます。テキスト環境のブラウザで見るときにも日付があると結構見やすいですし(日記の切れ目が分かりやすい……気がする)。

編集者:すずき(2023/11/07 01:09)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2023年11月6日 >>> 2023年10月24日
link もっと後

管理用メニュー

link 記事を新規作成

<2023>
<<<11>>>
---1234
567891011
12131415161718
19202122232425
2627282930--

最近のコメント5件

  • link 21年9月20日
    すずきさん (11/19 01:04)
    「It was my pleasure.」
  • link 21年9月20日
    whtさん (11/17 23:41)
    「This blog solves my ...」
  • link 24年10月1日
    すずきさん (10/06 03:41)
    「xrdpで十分動作しているので、Wayl...」
  • link 24年10月1日
    hdkさん (10/03 19:05)
    「GNOMEをお使いでしたら今はWayla...」
  • link 24年10月1日
    すずきさん (10/03 10:12)
    「私は逆にVNCサーバーに繋ぐ使い方をした...」

最近の記事20件

  • link 23年4月10日
    すずき (11/15 23:48)
    「[Linux - まとめリンク] 目次: Linux関係の深いまとめリンク。目次: RISC-V目次: ROCK64/ROCK...」
  • link 24年11月6日
    すずき (11/15 23:47)
    「[Ubuntu 24.04 LTS on ThinkPad X1 Carbon Gen 12] 目次: Linux会社ではTh...」
  • link 24年11月11日
    すずき (11/15 23:26)
    「[Pythonのテストフレームワーク] 目次: Python最近Pythonを触ることが増えたのでテストについて調べようと思い...」
  • link 24年11月2日
    すずき (11/15 23:25)
    「[Python - まとめリンク] 目次: Python一覧が欲しくなったので作りました。 スクリプト言語始めました(Pyth...」
  • link 20年5月10日
    すずき (11/15 23:24)
    「[Pythonの文字置換APIは変な名前] 目次: PythonPythonの文字列置換は "string".replace(...」
  • link 24年2月7日
    すずき (11/15 23:23)
    「[複数の音声ファイルのラウドネスを統一したい] 目次: PythonPCやデジタル音楽プレーヤーで音楽を聞いていると、曲によっ...」
  • link 13年7月2日
    すずき (11/15 23:22)
    「[スクリプト言語始めました(PythonとRubyでNクイーン問題)] 目次: ベンチマーク目次: Pythonスクリプト言語...」
  • link 23年9月18日
    すずき (11/15 23:22)
    「[一覧の一覧 - まとめリンク] 一覧の一覧、まとめのまとめが欲しくなったので作りました。OS、アーキテクチャ系。目次: An...」
  • link 13年10月1日
    すずき (11/15 23:21)
    「[JetBrains PyCharm 3.0リリース] 目次: PythonPyCharmがメジャーアップデートされ PyCh...」
  • link 22年7月8日
    すずき (11/08 23:28)
    「[マンガ紹介 - まとめリンク] 目次: マンガ紹介面白かった漫画の紹介です。知名度はあまり気にせず紹介します。5作品乙女ゲー...」
  • link 24年10月31日
    すずき (11/04 15:17)
    「[DENSOの最終勤務日] 最終勤務日でした、入門カードや会社のPCを返却してきました。在籍期間はNSITEXE(品川のオフィ...」
  • link 24年10月30日
    すずき (11/02 20:33)
    「[マンガ紹介] 目次: マンガ紹介お気に入りのマンガ紹介シリーズ。最近完結した短めの作品を紹介します。マイナススキル持ち四人が...」
  • link 19年3月28日
    すずき (11/02 13:27)
    「[マンガ紹介] 目次: マンガ紹介お気に入りのマンガ紹介シリーズ。こわもてかわもて(全2巻、2019年)(アマゾンへのリンク)...」
  • link 21年6月20日
    すずき (11/02 13:22)
    「[読書一生分が93万円?] 目次: マンガ紹介書籍通販のhontoがこんなキャンペーンをやっています。honto読書一生分プレ...」
  • link 17年10月27日
    すずき (11/02 13:11)
    「[異世界&最強系漫画の種類] 目次: マンガ紹介少し前にアニメ化されて盛り上がって(おそらく負の方向に…)いた「...」
  • link 24年10月28日
    すずき (10/30 23:49)
    「[Linuxからリモートデスクトップ] 目次: Linux開発用のLinuxマシンの画面を見るにはいろいろな手段がありますが、...」
  • link 24年10月24日
    すずき (10/25 02:35)
    「[ONKYOからM-AUDIOのUSB DACへ] 目次: PCかれこれ10年以上(2013年3月16日の日記参照)活躍してく...」
  • link 24年7月25日
    すずき (10/25 02:24)
    「[OpenSBIを調べる - デバイスツリーの扱い(別方法)] 目次: LinuxOpenSBIのブート部分を調べます。Ope...」
  • link 24年8月7日
    すずき (10/25 02:23)
    「[Debian独自の挙動をするQEMUとbinfmt_misc] 目次: Linux前回はbinfmt_miscの使い方や動作...」
  • link 24年9月9日
    すずき (10/25 02:22)
    「[GDBの便利コマンド] 目次: LinuxGDBは便利ですが、少し使わないでいるとあっという間にコマンドを忘れます。便利&使...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 11/19 01:04