SVX日記
2008-07-03(Thu) Fedora9、SSDチューン
……というのがネックである。特にチョットずつアチコチに書き込む、という処理が苦手で、これは避けがたい事実である。一方でSLCタイプのフラッシュは、ほとんどHDDと遜色ない性能を発揮するので、連続書き込みのアドバンテージが際だつことから、あらゆる面でHDDを圧倒、という印象になる。
近年のOSには、もれなくディスクへの書き出しを遅延する機能が備わっている。例えば、文書を保存、とかやって、保存完了、と画面に出ても、その時点ではディスクには書き込まれていないのだ。OSは、書き込まなければならない内容をメモリ上に覚えておいて、ワザと、しばらく放置しておくのであった。
この動作は、保存した直後に、誤字に気づいて修正し、再保存されるような状況に効果を発揮する。修正後の内容を一度だけ書き込みば済むからだ。また、ファイルが削除される場合にも有効だ。書き込み自体をやめてしまうことができる。
つまり「ドカンと書き込むと、30秒後に、実際にドカンと書き込まれる」ということだ。書き込み能力の低いメディアにドカンと書くと、長時間、書き込み以外の仕事ができなくなってしまい、結果として、読みたい人が待たされてしまうことになる。書くのと違って、読めないと処理が先に進まない。これが、レスポンスの低下として表面化する。
ここで、スルドいLinuxマスターは「そんなコトできたっけ?」と思うだろうが、普通はそんなコトはできない。しかし、オープンソースなら、改造してしまえばいいのだ。つまりは、オリジナルカーネルをビルドしてしまうのである。
具体的には「mm/page-writeback.c」の改造である。dirty(ディスクに未書き出し)ページをゆっくりwriteback(書き出し)するようにする。一度にwritebackするページ数はMAX_WRITEBACK_PAGESという定数で1024に設定されているが、これをprocファイルシステムから可変化してしまう。
それ以外にも、dirtyページのメモリに占める割合が一定(dirty_ratio:デフォルト10%)を超えた状態を契機にwritebackを開始するものもある。この処理はbalance_dirty_pages関数で行われるので、以後、この動作をbalanceモードと呼ぶ。
各々、MAX_WRITEBACK_PAGESを最大処理ページ数としてwritebackするが、dirtyページが多い場合は即座にwritebackが繰り返される。書き込みが混雑している(congestion)と判断された場合、繰り返しごとに(HZ/10:0.1秒)ずつ、ウェイトが入るものの、これだけではジョロジョロとした書き込みにはならず、やはりドカンという感じになってしまう。
dirty_writeback_centisecs(500): kupdate, bdflushモードの起動周期(1/100sec)
dirty_expire_centisecs(3000): dirtyページの存在時間(1/100sec)(kupdateの動作契機)
dirty_background_ratio(5): bdflushモードの動作契機(%)
dirty_ratio(10): balanceモードの動作契機(%)
kupdate_max_writeback_pages(1024): kupdateモードの最大処理数(Page)(MAX_WRITEBACK_PAGESの可変化)
bdflush_max_writeback_pages(1024): bdflushモードの最大処理数(Page)(MAX_WRITEBACK_PAGESの可変化)
kupdate_writeback_once(0): kupdateモードの連続writeback抑制(Boolean)
bdflush_writeback_once(0): bdflushモードの連続writeback抑制(Boolean)
kupdate_congestion_wait(100): kupdateモードの書き込み混雑時のウェイト(ms)
bdflush_congestion_wait(100): bdflushモードの書き込み混雑時のウェイト(ms)
balance_congestion_wait(100): balanceモードの書き込み混雑時のウェイト(ms)
kupdate_call_count: kupdateモードの起動回数
bdflush_call_count: bdflushモードの起動回数
balance_call_count: balanceモードの起動回数
kupdate_writeback_count: kupdateモードのwriteback処理回数
bdflush_writeback_count: bdflushモードのwriteback処理回数
balance_writeback_count: balanceモードのwriteback処理回数
この機能を追加するのが以下のパッチ。一応、kernel-2.6.25.6-55.fc9.src.rpm用。linux-2.6-add-writeback-throttle.patchというパッチ名で保存しておこう。
diff -U 3 -r linux-2.6.25.i386.org/drivers/mmc/core/mmc.c linux-2.6.25.i386/drivers/mmc/core/mmc.c
--- linux-2.6.25.i386.org/drivers/mmc/core/mmc.c 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/drivers/mmc/core/mmc.c 2008-07-03 12:31:22.000000000 +0900
@@ -509,6 +509,7 @@
mmc_claim_host(host);
if (!mmc_host_is_spi(host))
mmc_deselect_cards(host);
+ printk(KERN_INFO "mmc_core: deselected mmc card for suspend.\n");
host->card->state &= ~MMC_STATE_HIGHSPEED;
mmc_release_host(host);
}
@@ -527,7 +528,9 @@
BUG_ON(!host->card);
mmc_claim_host(host);
+/* msleep(1000); */
err = mmc_init_card(host, host->ocr, host->card);
+ printk(KERN_INFO "mmc_core: reinitialised mmc card for resume. state=%d.\n", err);
mmc_release_host(host);
if (err) {
diff -U 3 -r linux-2.6.25.i386.org/drivers/mmc/core/sd.c linux-2.6.25.i386/drivers/mmc/core/sd.c
--- linux-2.6.25.i386.org/drivers/mmc/core/sd.c 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/drivers/mmc/core/sd.c 2008-07-03 12:31:08.000000000 +0900
@@ -571,6 +571,7 @@
mmc_claim_host(host);
if (!mmc_host_is_spi(host))
mmc_deselect_cards(host);
+ printk(KERN_INFO "mmc_core: deselected sd card for suspend.\n");
host->card->state &= ~MMC_STATE_HIGHSPEED;
mmc_release_host(host);
}
@@ -589,7 +590,9 @@
BUG_ON(!host->card);
mmc_claim_host(host);
+/* msleep(1000); */
err = mmc_sd_init_card(host, host->ocr, host->card);
+ printk(KERN_INFO "mmc_core: reinitialised sd card for resume. state=%d.\n", err);
mmc_release_host(host);
if (err) {
diff -U 3 -r linux-2.6.25.i386.org/drivers/mmc/host/sdhci.c linux-2.6.25.i386/drivers/mmc/host/sdhci.c
--- linux-2.6.25.i386.org/drivers/mmc/host/sdhci.c 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/drivers/mmc/host/sdhci.c 2008-07-03 07:46:09.000000000 +0900
@@ -1184,7 +1184,7 @@
if (!chip)
return 0;
- DBG("Suspending...\n");
+ printk(KERN_INFO DRIVER_NAME ": Suspending...\n");
for (i = 0;i < chip->num_slots;i++) {
if (!chip->hosts[i])
@@ -1221,7 +1221,7 @@
if (!chip)
return 0;
- DBG("Resuming...\n");
+ printk(KERN_INFO DRIVER_NAME ": Resuming...\n");
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
diff -U 3 -r linux-2.6.25.i386.org/include/linux/sysctl.h linux-2.6.25.i386/include/linux/sysctl.h
--- linux-2.6.25.i386.org/include/linux/sysctl.h 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/include/linux/sysctl.h 2008-07-03 08:02:36.000000000 +0900
@@ -205,6 +205,25 @@
VM_PANIC_ON_OOM=33, /* panic at out-of-memory */
VM_VDSO_ENABLED=34, /* map VDSO into new processes? */
VM_MIN_SLAB=35, /* Percent pages ignored by zone reclaim */
+
+ VM_BDFLUSH_MAX_WRITEBACK_PAGES=36,
+ VM_KUPDATE_MAX_WRITEBACK_PAGES=37,
+
+ VM_BALANCE_CALL_COUNT=38,
+ VM_BDFLUSH_CALL_COUNT=39,
+ VM_KUPDATE_CALL_COUNT=40,
+
+ VM_BALANCE_WRITEBACK_COUNT=41,
+ VM_BDFLUSH_WRITEBACK_COUNT=42,
+ VM_KUPDATE_WRITEBACK_COUNT=43,
+
+ VM_BALANCE_CONGESTION_WAIT=44,
+ VM_BDFLUSH_CONGESTION_WAIT=45,
+ VM_KUPDATE_CONGESTION_WAIT=46,
+
+ VM_BDFLUSH_WRITEBACK_ONCE=47,
+ VM_KUPDATE_WRITEBACK_ONCE=48,
+ VM_KUPDATE_SYNC_SUPERS=49,
};
diff -U 3 -r linux-2.6.25.i386.org/include/linux/writeback.h linux-2.6.25.i386/include/linux/writeback.h
--- linux-2.6.25.i386.org/include/linux/writeback.h 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/include/linux/writeback.h 2008-07-03 08:02:29.000000000 +0900
@@ -105,6 +105,25 @@
extern int block_dump;
extern int laptop_mode;
+extern int bdflush_max_writeback_pages;
+extern int kupdate_max_writeback_pages;
+
+extern int balance_call_count;
+extern int bdflush_call_count;
+extern int kupdate_call_count;
+
+extern int balance_writeback_count;
+extern int bdflush_writeback_count;
+extern int kupdate_writeback_count;
+
+extern int balance_congestion_wait;
+extern int bdflush_congestion_wait;
+extern int kupdate_congestion_wait;
+
+extern int bdflush_writeback_once;
+extern int kupdate_writeback_once;
+extern int kupdate_sync_supers;
+
extern int dirty_ratio_handler(struct ctl_table *table, int write,
struct file *filp, void __user *buffer, size_t *lenp,
loff_t *ppos);
diff -U 3 -r linux-2.6.25.i386.org/kernel/sysctl.c linux-2.6.25.i386/kernel/sysctl.c
--- linux-2.6.25.i386.org/kernel/sysctl.c 2008-07-02 15:58:31.000000000 +0900
+++ linux-2.6.25.i386/kernel/sysctl.c 2008-07-03 08:06:14.000000000 +0900
@@ -951,6 +951,118 @@
.proc_handler = &proc_dointvec_userhz_jiffies,
},
{
+ .ctl_name = VM_BDFLUSH_MAX_WRITEBACK_PAGES,
+ .procname = "bdflush_max_writeback_pages",
+ .data = &bdflush_max_writeback_pages,
+ .maxlen = sizeof(bdflush_max_writeback_pages),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_KUPDATE_MAX_WRITEBACK_PAGES,
+ .procname = "kupdate_max_writeback_pages",
+ .data = &kupdate_max_writeback_pages,
+ .maxlen = sizeof(kupdate_max_writeback_pages),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BALANCE_CALL_COUNT,
+ .procname = "balance_call_count",
+ .data = &balance_call_count,
+ .maxlen = sizeof(balance_call_count),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BDFLUSH_CALL_COUNT,
+ .procname = "bdflush_call_count",
+ .data = &bdflush_call_count,
+ .maxlen = sizeof(bdflush_call_count),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_KUPDATE_CALL_COUNT,
+ .procname = "kupdate_call_count",
+ .data = &kupdate_call_count,
+ .maxlen = sizeof(kupdate_call_count),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BALANCE_WRITEBACK_COUNT,
+ .procname = "balance_writeback_count",
+ .data = &balance_writeback_count,
+ .maxlen = sizeof(balance_writeback_count),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BDFLUSH_WRITEBACK_COUNT,
+ .procname = "bdflush_writeback_count",
+ .data = &bdflush_writeback_count,
+ .maxlen = sizeof(bdflush_writeback_count),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_KUPDATE_WRITEBACK_COUNT,
+ .procname = "kupdate_writeback_count",
+ .data = &kupdate_writeback_count,
+ .maxlen = sizeof(kupdate_writeback_count),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BALANCE_CONGESTION_WAIT,
+ .procname = "balance_congestion_wait",
+ .data = &balance_congestion_wait,
+ .maxlen = sizeof(balance_congestion_wait),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BDFLUSH_CONGESTION_WAIT,
+ .procname = "bdflush_congestion_wait",
+ .data = &bdflush_congestion_wait,
+ .maxlen = sizeof(bdflush_congestion_wait),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_KUPDATE_CONGESTION_WAIT,
+ .procname = "kupdate_congestion_wait",
+ .data = &kupdate_congestion_wait,
+ .maxlen = sizeof(kupdate_congestion_wait),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_BDFLUSH_WRITEBACK_ONCE,
+ .procname = "bdflush_writeback_once",
+ .data = &bdflush_writeback_once,
+ .maxlen = sizeof(bdflush_writeback_once),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_KUPDATE_WRITEBACK_ONCE,
+ .procname = "kupdate_writeback_once",
+ .data = &kupdate_writeback_once,
+ .maxlen = sizeof(kupdate_writeback_once),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
+ .ctl_name = VM_KUPDATE_SYNC_SUPERS,
+ .procname = "kupdate_sync_supers",
+ .data = &kupdate_sync_supers,
+ .maxlen = sizeof(kupdate_sync_supers),
+ .mode = 0644,
+ .proc_handler = &proc_dointvec,
+ },
+ {
.ctl_name = VM_NR_PDFLUSH_THREADS,
.procname = "nr_pdflush_threads",
.data = &nr_pdflush_threads,
diff -U 3 -r linux-2.6.25.i386.org/kernel/sysctl_check.c linux-2.6.25.i386/kernel/sysctl_check.c
--- linux-2.6.25.i386.org/kernel/sysctl_check.c 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/kernel/sysctl_check.c 2008-07-03 08:07:24.000000000 +0900
@@ -136,6 +136,25 @@
{ VM_VDSO_ENABLED, "vdso_enabled" },
{ VM_MIN_SLAB, "min_slab_ratio" },
+ { VM_BDFLUSH_MAX_WRITEBACK_PAGES, "bdflush_max_writeback_pages" },
+ { VM_KUPDATE_MAX_WRITEBACK_PAGES, "kupdate_max_writeback_pages" },
+
+ { VM_BALANCE_CALL_COUNT, "balance_call_count" },
+ { VM_BDFLUSH_CALL_COUNT, "bdflush_call_count" },
+ { VM_KUPDATE_CALL_COUNT, "kupdate_call_count" },
+
+ { VM_BALANCE_WRITEBACK_COUNT, "balance_writeback_count" },
+ { VM_BDFLUSH_WRITEBACK_COUNT, "bdflush_writeback_count" },
+ { VM_KUPDATE_WRITEBACK_COUNT, "kupdate_writeback_count" },
+
+ { VM_BALANCE_CONGESTION_WAIT, "balance_congestion_wait" },
+ { VM_BDFLUSH_CONGESTION_WAIT, "bdflush_congestion_wait" },
+ { VM_KUPDATE_CONGESTION_WAIT, "kupdate_congestion_wait" },
+
+ { VM_BDFLUSH_WRITEBACK_ONCE, "bdflush_writeback_once" },
+ { VM_KUPDATE_WRITEBACK_ONCE, "kupdate_writeback_once" },
+ { VM_KUPDATE_SYNC_SUPERS, "kupdate_sync_supers" },
+
{}
};
diff -U 3 -r linux-2.6.25.i386.org/mm/page-writeback.c linux-2.6.25.i386/mm/page-writeback.c
--- linux-2.6.25.i386.org/mm/page-writeback.c 2008-04-17 11:49:44.000000000 +0900
+++ linux-2.6.25.i386/mm/page-writeback.c 2008-07-03 08:18:33.000000000 +0900
@@ -44,6 +44,25 @@
*/
#define MAX_WRITEBACK_PAGES 1024
+int bdflush_max_writeback_pages = 1024;
+int kupdate_max_writeback_pages = 1024;
+
+int balance_call_count = 0;
+int bdflush_call_count = 0;
+int kupdate_call_count = 0;
+
+int balance_writeback_count = 0;
+int bdflush_writeback_count = 0;
+int kupdate_writeback_count = 0;
+
+int balance_congestion_wait = HZ/10;
+int bdflush_congestion_wait = HZ/10;
+int kupdate_congestion_wait = HZ/10;
+
+int bdflush_writeback_once = 0;
+int kupdate_writeback_once = 0;
+int kupdate_sync_supers = 1;
+
/*
* After a CPU has dirtied this many pages, balance_dirty_pages_ratelimited
* will look to see if it needs to force writeback or throttling.
@@ -366,6 +385,8 @@
struct backing_dev_info *bdi = mapping->backing_dev_info;
+ balance_call_count++;
+
for (;;) {
struct writeback_control wbc = {
.bdi = bdi,
@@ -408,6 +429,7 @@
*/
if (bdi_nr_reclaimable) {
writeback_inodes(&wbc);
+ balance_writeback_count++;
pages_written += write_chunk - wbc.nr_to_write;
get_dirty_limits(&background_thresh, &dirty_thresh,
&bdi_thresh, bdi);
@@ -436,7 +458,7 @@
if (pages_written >= write_chunk)
break; /* We've done our duty */
- congestion_wait(WRITE, HZ/10);
+ congestion_wait(WRITE, balance_congestion_wait);
}
if (bdi_nr_reclaimable + bdi_nr_writeback < bdi_thresh &&
@@ -558,6 +580,8 @@
.range_cyclic = 1,
};
+ bdflush_call_count++;
+
for ( ; ; ) {
long background_thresh;
long dirty_thresh;
@@ -569,14 +593,17 @@
break;
wbc.more_io = 0;
wbc.encountered_congestion = 0;
- wbc.nr_to_write = MAX_WRITEBACK_PAGES;
+ wbc.nr_to_write = bdflush_max_writeback_pages;
wbc.pages_skipped = 0;
writeback_inodes(&wbc);
- min_pages -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;
+ bdflush_writeback_count++;
+ if (bdflush_writeback_once)
+ break;
+ min_pages -= bdflush_max_writeback_pages - wbc.nr_to_write;
if (wbc.nr_to_write > 0 || wbc.pages_skipped > 0) {
/* Wrote less than expected */
if (wbc.encountered_congestion || wbc.more_io)
- congestion_wait(WRITE, HZ/10);
+ congestion_wait(WRITE, bdflush_congestion_wait);
else
break;
}
@@ -633,7 +660,10 @@
.range_cyclic = 1,
};
- sync_supers();
+ kupdate_call_count++;
+
+ if(kupdate_sync_supers)
+ sync_supers();
oldest_jif = jiffies - dirty_expire_interval;
start_jif = jiffies;
@@ -644,15 +674,18 @@
while (nr_to_write > 0) {
wbc.more_io = 0;
wbc.encountered_congestion = 0;
- wbc.nr_to_write = MAX_WRITEBACK_PAGES;
+ wbc.nr_to_write = kupdate_max_writeback_pages;
writeback_inodes(&wbc);
+ kupdate_writeback_count++;
+ if (kupdate_writeback_once)
+ break;
if (wbc.nr_to_write > 0) {
if (wbc.encountered_congestion || wbc.more_io)
- congestion_wait(WRITE, HZ/10);
+ congestion_wait(WRITE, kupdate_congestion_wait);
else
break; /* All the old data is written */
}
- nr_to_write -= MAX_WRITEBACK_PAGES - wbc.nr_to_write;
+ nr_to_write -= kupdate_max_writeback_pages - wbc.nr_to_write;
}
if (time_before(next_jif, jiffies + HZ))
next_jif = jiffies + HZ;
raven.itline.jp:/usr/src # rpm -ivh kernel-2.6.25.6-55.fc9.src.rpm
raven.itline.jp:/usr/src # cp linux-2.6-add-writeback-throttle.patch redhat/SOURCES/
raven.itline.jp:/usr/src/redhat/SPECS # diff -c kernel.spec kernel.my56.spec
*** kernel.spec 2008-06-10 21:54:24.000000000 +0900
--- kernel.my56.spec 2008-07-01 21:30:12.000000000 +0900
***************
*** 21,27 ****
# works out to the offset from the rebase, so it doesn't get too ginormous.
#
%define fedora_cvs_origin 619
! %define fedora_build %(R="$Revision: 1.674 $"; R="${R%% \$}"; R="${R##: 1.}"; expr $R - %{fedora_cvs_origin})
# base_sublevel is the kernel version we're starting with and patching
# on top of -- for example, 2.6.22-rc7-git1 starts with a 2.6.21 base,
--- 21,27 ----
# works out to the offset from the rebase, so it doesn't get too ginormous.
#
%define fedora_cvs_origin 619
! %define fedora_build %(R="$Revision: 1.675 $"; R="${R%% \$}"; R="${R##: 1.}"; expr $R - %{fedora_cvs_origin})
# base_sublevel is the kernel version we're starting with and patching
# on top of -- for example, 2.6.22-rc7-git1 starts with a 2.6.21 base,
***************
*** 689,694 ****
--- 689,696 ----
# get rid of imacfb and make efifb work everywhere it was used
Patch2600: linux-2.6-merge-efifb-imacfb.patch
+ Patch3000: linux-2.6-add-writeback-throttle.patch
+
%endif
BuildRoot: %{_tmppath}/kernel-%{KVERREL}-root
***************
*** 1254,1259 ****
--- 1256,1263 ----
# get rid of imacfb and make efifb work everywhere it was used
ApplyPatch linux-2.6-merge-efifb-imacfb.patch
+ ApplyPatch linux-2.6-add-writeback-throttle.patch
+
# ---------- below all scheduled for 2.6.24 -----------------
# END OF PATCH APPLICATIONS
raven.itline.jp:/usr/src # vi redhat/SOURCES/config-generic
raven.itline.jp:/usr/src/redhat/SOURCES # diff -c config-generic.org config-generic
*** config-generic.org 2008-05-20 17:19:52.000000000 +0900
--- config-generic 2008-07-03 12:47:22.000000000 +0900
***************
*** 104,110 ****
CONFIG_MMC_BLOCK_BOUNCE=y
CONFIG_SDIO_UART=m
# CONFIG_MMC_DEBUG is not set
! # CONFIG_MMC_UNSAFE_RESUME is not set
CONFIG_MMC_BLOCK=m
CONFIG_MMC_RICOH_MMC=m
CONFIG_MMC_SDHCI=m
--- 104,110 ----
CONFIG_MMC_BLOCK_BOUNCE=y
CONFIG_SDIO_UART=m
# CONFIG_MMC_DEBUG is not set
! CONFIG_MMC_UNSAFE_RESUME=y
CONFIG_MMC_BLOCK=m
CONFIG_MMC_RICOH_MMC=m
CONFIG_MMC_SDHCI=m
ちなみに、冒頭の「エラくハマっていた」のは、ここでスケベ心を出して、このconfig-genericの修正も、パッチファイルに含めてしまおうとした結果であった。パッチは「config-generic等を元に、最終的なconfigが生成された『後』で当たる」のである。つまり、どんなにガンバってもCONFIG_MMC_UNSAFE_RESUMEはセットされないのである。
よって、config-genericは「直接に書き換える」こと。オイラはこれに気づくまで、何度もSDカードのパーティションテーブルをぶっ壊しては、復旧しては、mmcドライバにデバッグ行を追加しては、カーネルのコンパイルを繰り返しては……と、地獄を這い回りまくっていた。おかげで、ファイルシステムに詳しくなり、mmcドライバの処理の大半が頭に入ってしまったぞ。
raven.itline.jp:/usr/src # time rpmbuild -ba --target=i686 --with baseonly --without debuginfo /usr/src/redhat/SPECS/kernel.my56.spec
2008-07-14(Mon) Fedora9、ハーバードチューン
まず、オイラが使っているSSDというか、コンパクトフラッシュの性能を考察する。Linuxを動かしている都合上、Windows用のベンチマークアプリであるCrystalDiskMarkの結果を引用するのはおかしいのだが、同アプリはフラッシュメディアの性能を測る上で事実上の標準となっており、Web上から結果が入手しやすいから仕方ない。
「不動の地雷評価」
……を築いている「A-DATA Speedy 16G」である。Web上にて、以下の結果を発見した。
Sequential Read : 14.871 MB/s
Sequential Write : 7.124 MB/s
Random Read 512KB : 14.885 MB/s
Random Write 512KB : 1.806 MB/s
Random Read 4KB : 7.859 MB/s
Random Write 4KB : 0.018 MB/s
まずは、Read。このコンパクトフラッシュはUltra DMAモードをサポートしていない。最高でMultiword DMAのmode2までだ。このモードでの理論上の最高転送速度は16.6MB/s。よって、Readの15MB/s弱というのは、フラッシュの性能ではなく、帯域の性能による制限ということになる。
しかしながら、Random Read 4KBの7.859 MB/sは、ハードディスクにとっては「ドギモを抜く」性能である。最新の高速HDD「VelociRaptor」でも1MB/sがやっとだからだ。動く部位(ヘッド)のないフラッシュならではのスコアといえよう。
7.859MB/s * 1024 / 4KB = 2012times/s = 0.50ms
秒間2000回シークでき、シークタイムは0.5msである、と。これは、平均シークタイム5ms程度が「物理的限界」であるハードディスクの、軽く10倍の性能である。最底辺のフラッシュでさえだ。恐ろしく高性能といえよう。
確かに、Sequential Readの絶対値は速くはないものの、通常、アプリの立ち上げ等では、HDDがガリガリいうことからも、Random Readが大きくモノをいう。ベッタリと数百メガのムービー等を外部にコピーする場合などを除けば、Read面ではそこそこ十分な性能といえよう。
0.018MB/s * 1024 / 4KB = 4.608times/s
1.806MB/s * 1024 / 512KB = 3.612times/s
4KBの書き込みと512KBの書き込み「回数」には大差がない。量的には100倍以上の差があるのに、である。そこで、もっとドカンと書き込んでいるSequential Writeの結果から、どこまでが「1回」なのかを求めてみる。
7.124MB/s * 1024 / 3.612times/s = 2019.65KB/s
露骨な結果が出た。2048KB。つまり、2MBまでが1回であろう。逆に言うと、一度に2MBを書き込むのがもっとも効率がよく、4KBずつ書き込むと「秒間5ページを書き込むことができない」ということである。たったの5ページ。こいつぁキッツい制限といえよう。
ちょっと脱線するが、チマタでは8GBのコンパクトフラッシュに比較し、16GBのものが性能が低い、という評価が出回っているが、これは構造上、書き込み単位が倍になっているためと思われる。書き込み単位が倍になれば、Random Writeの書き込み速度が半分になるのも道理であろう。
なんにせよ、OSの動作がもっさりとしてしまう要因はココにあるワケだ。通常、処理を行う際には何にをするにもReadが伴う。デバイスがWriteにかかりっきりになっていれば、Readは遅れ、それは処理の遅延、要するにもっさりに直結するのである。
前回も書いたが、Linuxを含む近年のOSではデバイスに対して書き込みを遅延する。いまこの手にデータがなければ一歩も処理を先に進めることができないReadと違い、Writeなんてのは余剰なメモリに溜め込んでおいてヒマな時にやればいいという思想である。
そこで私は、前回「遅延分をジョロジョロと書き込む」改造と、ReadをWriteに優先させまくる性格を持つI/Oスケジューラであるdeadlineを組み合わせ、デバイスがWriteにかかりっきりになる状態を防ぐべく、チューニングを繰り返したのである……んが、結論から言うと、どうにもできなかった。
というのも、Read面ではそこそこ十分な性能といえる15MB/sだが、秒間2回の書き込みがあると、理論上の性能は半分に、秒間4回の書き込みがあると、理論上はゼロに、もっというと、秒間8回の書き込みがあると、2秒間の遅延が発生してしまう。
上述のとおり、デバイスへの要求は「回数」の観点で制限し、帯域を保護すべきだが、オイラが改造を加えたpage-writeback.cのレイヤでは「ページ(4KB)」単位での制御しかできない。だからといって、最低ラインである秒間4回に配慮して、kupdate_max_writeback_pagesに2、dirty_writeback_centisecsに100を指定して毎秒書き込みを指定すると、100MBの書き込みを処理し終えるのに、2時間弱もかかってしまう。
このヘンが判明したあたりで、いーかげん、レスポンスの悪さに嫌気が差してきたので、8GBでx300なコンパクトフラッシュを買い「お金で解決」してしまおうかとも思ったのだが、それはそれで負けを認めるようで悔しい……そこで、新たに別の手段として、ReadデバイスとWriteデバイスを分割するという手を思いついた。
問題は、チョロチョロとしたWriteでさえ、デバイスのRead帯域を大幅に圧迫してしまうため、あらゆるページインが遅延してしまうという問題である。だが、それならば、Writeしがちな領域を別のデバイスに割り振ってしまい、結果的にWriteをゼロにしてしまえばいいのではないか?
ちょっと大げさではあるが、これはハーバードアーキテクチャ化といえるだろう。事実「命令アクセスとデータアクセスを分離することで高速化できる」というのは、ハーバードアーキテクチャの利点のひとつであり、それはまさに今回の狙いそのものである。
具体的には、別途装備してある16GBのSDカードに/varと/tmpを移動してしまう。既にSDカードは/home領域に使っているが、その用途であればOS自体の挙動に伴ってReadが発生する可能性は低い。
早速、シングルユーザモードに落ちて、/varと/tmpをSDカード側に移動する。/varと/tmpを押さえつけるのが目的ではないので、mountのbind機能を使って元の位置にぶら下げる。/etc/fstabには、以下のようなエントリを加えた。
/mnt/mmc/home /home none bind 0 0
/mnt/mmc/tmp /tmp none bind 0 0
/mnt/mmc/var /var none bind 0 0
# vi /etc/sysctl.conf
vm.dirty_ratio = 80
vm.dirty_background_ratio = 50
vm.dirty_writeback_centisecs = 1500
vm.dirty_expire_centisecs = 9000
# sysctl -p
# echo 8192 > /sys/block/mmcblk0/queue/nr_requests
I/Oスケジューラのタイプをdeadlineに変更する。deadlineスケジューラは、Write要求を遅延し、Read要求を優先する性格のスケジューラである。deadlineスケジューラのパラメータであるfifo_batchは、連続する要求をキューに押し込む限界数、writes_starvedは、Write要求を差し置いてRead要求を優先する限界数で、これらは増加した。その他のread_expire, write_expire, front_mergesはデフォルトのままとした。
# echo deadline > /sys/block/mmcblk0/queue/scheduler
# echo 128 > /sys/block/mmcblk0/queue/iosched/fifo_batch
# echo 8 > /sys/block/mmcblk0/queue/iosched/writes_starved
echo 8192 > /sys/block/mmcblk0/queue/nr_requests
echo deadline > /sys/block/mmcblk0/queue/scheduler
echo 128 > /sys/block/mmcblk0/queue/iosched/fifo_batch
echo 8 > /sys/block/mmcblk0/queue/iosched/writes_starved
2008-07-23(Wed) Fedora9、iiimecfチューン
つーわけで、emacsである。オイラのemacsとの付き合いは電脳倶楽部から始まった……と、一見意味不明ながら、これでピンときてしまう、オイラと同じ「その筋」な人は決して少なくないに違いないと思うので、あえて説明は略す。
そりゃ、別にemacs上でフツーにATOKを起動したって日本語は打てる。んが、その場合、2ストロークキーを使う際、イチイチATOKをオフにする必要が生じ、実質、使い物にならない。キーボードの上に何層にもバラ撒いてある大量の機能を体で使いこなしてこそemacsを使う価値がある。決してマウスなんかでメニューを触わってはイカんのである。
# tar xvfz IIIMECF-0.75.tar.gz
# cd iiimecf
diff -c lisp.org/iiimcf.el lisp/iiimcf.el
*** lisp.org/iiimcf.el 2007-12-09 08:54:54.000000000 +0900
--- lisp/iiimcf.el 2008-06-05 12:54:41.000000000 +0900
***************
*** 284,289 ****
--- 284,305 ----
(defvar iiimcf-keycode-spec-alist
`((13 10 0)
+
+ (11 37 65535) ; Ctrl + k
+ (12 39 65535) ; Ctrl + l
+ (14 40 65535) ; Ctrl + n
+
+ (21 117 65535) ; Ctrl + u
+ (9 118 65535) ; Ctrl + i
+ (15 119 65535) ; Ctrl + o
+ (16 120 65535) ; Ctrl + p
+
+ (2 38 65535) ; Ctrl + b
+ (6 32 65535) ; Ctrl + f
+ (7 27 65535) ; Ctrl + g
+
+ (1 36 65535) ; Ctrl + a
+
(32 32)
,@(mapcar #'(lambda (x) (list x x 0))
(iiimcf-numseq 1 31))
# emacs -q --no-site-file -batch -l iiimcf-comp.el
# mkdir /usr/share/emacs/site-lisp/iiimecf
# cp lisp/* /usr/share/emacs/site-lisp/iiimecf
;; .emacs
;;; uncomment this line to disable loading of "default.el" at startup
;; (setq inhibit-default-init t)
;; enable visual feedback on selections
;(setq transient-mark-mode t)
;; default to better frame titles
(setq frame-title-format
(concat "%b - emacs@" (system-name)))
;; default to unified diffs
(setq diff-switches "-u")
;; always end a file with a newline
;(setq require-final-newline 'query)
;;; uncomment for CJK utf-8 support for non-Asian users
;; (require 'un-define)
(global-set-key "\C-h" 'delete-backward-char)
(global-set-key "\C-z" 'scroll-down)
(global-set-key "\C-u" 'undo)
(global-set-key "\C-]" 'call-last-kbd-macro)
(menu-bar-mode nil)
(column-number-mode t)
(setq blink-matching-paren nil)
(setq default-tab-width 4)
(setq default-fill-column 64)
(setq text-mode-hook 'turn-on-auto-fill)
(setq make-backup-files nil)
(setq auto-save-default nil)
;; ATOK X3 for Linux
(setq iiimcf-server-control-hostlist '("unix:/tmp/.iiim-mitsu/:0.0"))
(require 'iiimcf-sc)
(setq iiimcf-server-control-default-language "ja")
(setq iiimcf-server-control-default-input-method "atokx3")
(setq default-input-method 'iiim-server-control)
(define-key global-map "\C-j" (lambda ()
(interactive)
(if current-input-method (inactivate-input-method))
(toggle-input-method)))
(define-key global-map "\C-o" (lambda ()
(interactive)
(inactivate-input-method)))
(setq iiimcf-UI-input-method-title-format "<ATOK:%s>")
(setq iiimcf-UI-preedit-use-face-p "window-system")
前回との差は、IMの起動キーを「Ctrl+J」でオン「Ctrl+O」でオフに変更したこと。結局「変換」で通常のATOKのオン/オフ、「Ctrl+変換」でemacsのATOKのオン/オフ、などとやってると、なまじ操作が近い分、混乱を招くだけであった。そこで、思い切って、オンとオフのキーを分離し、指の方を慣らすことにした。
んが、イザ使い始めると問題発生。全角の記号である「■★…」等が半角扱いになって表示が乱れてしまう。表示上の問題だけであれば、何とか我慢できないこともないが、この文字を出そうとするとiiimecfがコケてしまうからキツい……■だぁって使いたいぃ〜、●もぉ出したいぃ〜、きらっ★ 流星にぃ〜♪……てな感じで打てなければ戦争が終わらない。
どうも、emacs22からはMule-UCSというUTF8を扱うモジュールが不要になり、代わりに内蔵のutf-translate-cjkというモジュールを使うようになっており、それで起動が速くなった反面、微妙な動作の違いが問題として表面化しているということらしい。
結論から言うと、以下の設定を.emacsに追加すればいいらしい。出典はココ。
(utf-translate-cjk-set-unicode-range
'((#x00a2 . #x00a3) ;
(#x00a7 . #x00a8) ;
(#x00ac . #x00ac) ;
(#x00b0 . #x00b1) ;
(#x00b4 . #x00b4) ;
(#x00b6 . #x00b6) ;
(#x00d7 . #x00d7) ;
(#X00f7 . #x00f7) ;
(#x0370 . #x03ff) ; Greek and Coptic
(#x0400 . #x04FF) ; Cyrillic
(#x2000 . #x206F) ; General Punctuation
(#x2100 . #x214F) ; Letterlike Symbols
(#x2190 . #x21FF) ; Arrows
(#x2200 . #x22FF) ; Mathematical Operators
(#x2300 . #x23FF) ; Miscellaneous Technical
(#x2500 . #x257F) ; Box Drawing
(#x25A0 . #x25FF) ; Geometric Shapes
(#x2600 . #x26FF) ; Miscellaneous Symbols
(#x2e80 . #xd7a3) (#xff00 . #xffef)))
ブログ等、ブラウザでは基本的にプロポーショナルなフォントで文章を読むので大きな問題はないが、メール等では閲覧環境が等幅であることも少なくなく(我がmaveもそうだ)、その場合の読みやすさに配慮して、オイラは半角英単語の左右には半角スペースを入れることにしている。んが、IMをオフにしないと半角スペースが打てない環境だとすると、これはやってられないほど面倒である。個人的には大問題だ。
前回はTERATERMとiiimecfの両方の改造によって、どうにかこの動作を実現したが、今回はどうしてもダメ。相当の時間、iiimecfのソースをイジっては、試行錯誤を繰り返したのだが、ニッチもサッチもどころか、ヨッチもゴッチも(以下略)いかないったらない。
結局、またもやオープンソースの強みを発動してしまった。やや、究極の手段だが「作者の方に相談する」である。状況を説明し、どうにかヒントだけでも、とお願いしたところ、結局、ズバリの修正方法を教えてもらってしまった。方法は以下。
diff -c lisp.org/iiimcf.el lisp/iiimcf.el
*** lisp.org/iiimcf.el 2007-12-09 08:54:54.000000000 +0900
--- lisp/iiimcf.el 2008-06-05 12:54:41.000000000 +0900
***************
*** 565,570 ****
--- 581,587 ----
(char (logior (lsh (car kchar) 16)
(cdr kchar)))
key)
+ (if (and (= 1 mod) (= char 32)) (setq kmod '(0 . 0)))
(cond ((/= (car kcode) 0) nil)
((setq key (iiimcf-iiim-keycode-pre-translate
(cdr kcode)))
(global-set-key [?\S- ] 'iiimcf-server-control-keyforward)
作者の方によると、実装方法としてあまり美しくないということで、本家の開発ツリーにはこのパッチは取り込まれない可能性があるそうだ。いうなれば「iiimecf先生のパッチが読めるのはSVX日記だけ!!」という状態といえよう。うっしっし。
ただし、これにはまだ続きがある。言ってなかったが、オイラはemacsを-nw付きで常用している。つまり「別にGUIウィンドウを開く」のではなく「端末画面内にそのまま表示」させているのである。これで何か問題があるかというと、実はかなりの問題があって……
……のであった。これでは、如何にiiimecf側で対処してもらっても、どうすることもできない。当初は、どうにかShiftの押下を伝える方法があるのではないか、と軽く考えていたのだが、どうやらどうにも無理らしい。
……やっぱ、ヤブサカっすわ。フォントが複数使えるエディタは「マルチフォントエディタ」だが、これじゃ「まるっちいフォントエディタ」である。そもそもオイラは、アンチエイリアスフォントが今ほど一般的でなかった頃からの、筋金入りのOsakaアンチエイリアンである。今さら、ビットマッピンガーには戻れないのだ。決して。
……などと、ボヤくヒマがあったら、情報収集だ。同じコトを考えているヤツは大抵いるハズなのだ……と、あったッ!! XftGnuEmacsだ。