ホーム 主筆 その他ソフト その他情報 Syuhitu.org English

Windows関連

スクリーンセーバー作成法

半透明ウインドウの性能

bootfont.bin

キャビネット形式

ウインドウスタイルをいじる

Java製ソフトをServiceに登録する

イベントログにメッセージを出力する

コントロールパネルにアイコンを追加する

スクリプトによる拡張1

スクリプトによる拡張2

ガジェットの作成

大容量メモリ

メモリ搭載量の下限に挑む

スパースファイルにする

表示されるアイコンの種類を調べてみた

メモリマップIOとエラー処理

ファイルを作る順番と速度の関係

Cryptography API: Next Generationを使う

Windows 10のアクセントカラー

iSCSIディスクにバックアップを取る

サーバプロセスを分離して実装する

サーバプロセスを分離して実装する - F#

レジストリに大量に書き込む

Solaris関連

OpenGL

Solaris設定

ディレクトリの読み込み

主筆プラグイン開発

マルチスレッドでの開発

door

音を出す

Blade100の正しい虐め方

パッケージの作成

画像入出力

BMPファイル

ICOファイル

ANIファイル

JPEGファイル

減色アルゴリズム

減色アルゴリズムの並列化

その他アルゴリズムなど

自由軸回転

Base64

文字列操作

CPU利用率の取得

正規表現ライブラリ

メタボールを作る

メタボールを作る2

正規表現とNFA・DFA

C言語の構文解析

液晶ディスプレイを解体してみた

iSCSIの理論と実装

単一フォルダにファイルを沢山作る

USB-HUBのカスケード接続

SafeIntの性能

VHDファイルのフォーマット

USBメモリに書き込み続けてみた

レジストリに大量に書き込む

2016年4月3日公開

レジストリには大量に値を書き込まない方がいいという。

ではどれぐらいまでならいいのか?

前にやった、単一のフォルダに下にファイルを量産するテストと同じようなことをやってみた。

実行環境

試してみた環境は下記のとおりである。

ホスト側

項目
OSWindows 10 Pro 64bit
CPUCore i7 5930K 3.5GHz 6Core
メモリDDR-4 2666 32GB

上記の環境上で、Virtual Boxにより下記のOSを動作させている

項目
OSWindows 10 Pro 64bit
メモリ2GB
割り当てているコア数1Core

なお、仮想OS用のシステム領域は、7200回転3TBのHDD上に作成している。

単一のキーの下に値を大量に書き込む1

まずは、1つのキーの下に可能な限り値を登録することを考えてみる。

目標としてはDWORD型の値を100万個である。それを1,024個作るごとに実行時間を出力してみる。

レジストリに作成に使用したプログラムを以下に示す。

open Microsoft.Win32;;
open System;;

let foo ( key : RegistryKey ) idx =
     if idx % 1024 = 0 then
       printf "%s\n" ( DateTime.UtcNow.ToLocalTime().ToString( "HH:mm:ss.fff" ) )
     key.SetValue( sprintf "test-%08X\n" idx, idx );;
 
let key = Registry.CurrentUser.CreateSubKey( "Software\\test" );;
Seq.iter ( foo key ) { 0..1048576 };;
printf "ValueCount=%d\n" key.ValueCount;;
key.Close();;

「HKEY_CURRENT_USER\Software\test」のレジストリキーを開き、その下にひたすら値を登録してゆくだけである。

まず、開始前のレジストリの状態である。

当然「test」というレジストリキーは存在しない。

レジストリのファイルサイズ、つまりNTUSER.DATのサイズは大体1.5MB程度である。

さて、これで実行すると、途中で失敗する。

NTUSER.DATのサイズはこの時点で12MBに増大している。

レジストリエディタで確認すると、約26万個作成したところで失敗していることがわかる。

値を1,024個ずつ作成するのにかかった時間をグラフ化すると以下のようになる。

明らかに線形に劣化してしていることが分かる。

最初の1024個は数100msで作成しているのに対して、最後の方は15秒以上かかるようになっている。

これでは、たとえ失敗しなかったとしても、多分100万個分作るのは時間的に無理があるのではないかと思われる。

単一のキーの下に値を大量に書き込む2

さて、上のプログラムは異常終了した訳だが、とりあえず気を取り直してもう一度やり直してみる。

しかし同じことをやっても同じ結果にしかならないのは分かりきっているので、今度は値を1,025個ずつ作る都度、キーを開きなおすことにしてみる。

open Microsoft.Win32;;
open System;;

// 1025回繰り返す
Seq.iter ( fun idx -> 
 
  // 時刻を表示する
  printf "%s\n" ( DateTime.UtcNow.ToLocalTime().ToString( "HH:mm:ss.fff" ) )
 
  // レジストリキーを開く
  let key = Registry.CurrentUser.CreateSubKey( "Software\\test" )
 
  // 開いたレジストリキーの下に、値を1025個登録する
  Seq.iter ( fun n ->
    key.SetValue(
      sprintf "test-%08X\n" ( idx * 1024 + n ), idx * 1024 + n
    )
  ) { 0..1024 }
 
  // キーを閉じる
  key.Close()
 
) { 0..1024 };

何らかの明確な根拠があるわけではない。何となく、キーを開きハンドルを取得した後、そのハンドルを指定して100万個値を登録するのは無理があるのではないか。時々はハンドルを取得しなおした方がいいのではないか、となんとなく思っただけである。

ところで、1,024個ではなく1,025個ずつ作成しているのは、単なるバグである。間違えたまま実行してしまったので、そのまま記載しているだけである。

気を取り直して、実行前のレジストリのファイルサイズである。前の状態から継続しているため、12MB程度のままである。

これで実行すると、再び異常終了してしまった。

表示されている原因も同じようなもののようだ。

作成されたレジストリキーを見ると、前とまったく同じところで失敗している。

おそらく、プログラムのロジックの問題ではなく、単純に1つのキーの下に登録可能な値の数や容量に制限があるのだろうと考えられる。

とりあえず、実行後のNTUSER.DATのサイズと実行時間を示しておく。

作成にかかる時間の傾向も前と同じで、線形に劣化することがわかる。

複数のキーの下に値を登録する

1個のキーの下に登録できる値の数に制約があることはわかった。

だったら、登録する値を複数個のキーの配下に分けたらどうなるのか? 具体的には1,025個ずつの値を1,025個のキーに分けて登録してみることにする。

つまり、こういうイメージである。

プログラムは下記のとおりである。

open Microsoft.Win32;;
open System;;

// 1025個のキーを作成する
{ 0..1024 }
  |> Seq.iter ( fun idx -> 
       // ここで時刻を表示しておく
       printf "%s\n" ( DateTime.UtcNow.ToLocalTime().ToString( "HH:mm:ss.fff") )
 
       // キーを作成する
       let key = Registry.CurrentUser.CreateSubKey( sprintf "Software\\test-%08X" idx )
 
       // 作成したキーの配下に1025個の値を登録する
       { 0..1024 }
         |> Seq.iter ( fun n ->
              key.SetValue(
                sprintf "test-%08X\n" ( idx * 1024 + n ), idx * 1024 + n
              )
            ) 
 
       // キーを閉じる
       key.Close()
     )

今度は「HKEY_CURRENT_USER\Software\test-XXXXXXXX」のキーを作成して、その下に「test-XXXXXXXX」の値を登録する。

ところで、上のプログラムと似たような処理を行っている割にちょっとづつ書き方が違うのは、F#でのプログラムの作り方を試行錯誤しているからだ。あまり細かいことは気にするな。

実行前のレジストリのファイルサイズは約14MBである。何回かやり直しているから、前よりもちょっとだけ増えている。

当然だが、レジストリには書き込む対象となるキーや値は登録されていない状態で開始している。

さてこれで実行すると、今度はうまくいく。

実行時間としては、58.448秒だそうだ。

ファイルサイズは猛烈に増大している。約56MBまで膨れ上がっている。

レジストリには、下記のように値が書き込まれた

1,025個分の値を登録するのにかかった時間をグラフにすると下記のようになる。

たまに外れ値があるのはいいとして、キーや値の数に比例して性能が劣化するような傾向は見られず、良好な結果が得られた。

キーを100万個登録する

値を複数のキーに分散して配置すればたくさん登録できることはわかった。

ならば今度は、大量のキーを1つのキーの下に登録したらどうなるのかを試してみた。

最初の例から、登録する対象が値ではなくキーになっただけといえばそれまでである。

プログラムとしてはこんな感じである。

open Microsoft.Win32;;
open System;;

// 1025回繰り返す
{ 0..1024 }
  |> Seq.iter ( fun idx -> 
 
       // ここで時刻を表示しておく
       printf "%s\n" ( DateTime.UtcNow.ToLocalTime().ToString( "HH:mm:ss.fff") )
 
       // サブキーを開く
       let key = Registry.CurrentUser.CreateSubKey( "Software\\test" )
 
       // 1025回繰り返す
       { 0..1024 }
         |> Seq.iter ( fun n ->
              key.CreateSubKey( sprintf "test-%08X\n" ( idx * 1024 + n ) ).Close()
            )
 
       // キーを閉じる
       key.Close()
     )

そういえば、上のプログラムをコピペでここに書いているときに気が付いたのだが、やっぱり微妙にバグがあるようだ。

キーは1,025個×1,025回作っているのだが、キー名として「( idx * 1024 + n )」で計算しているから、1,025個に1つ名前が重複する。

まぁ、大勢に影響はないからいいか。

参考までに、実行前のファイルサイズは約116MBである。前からさらに増大しているのは、その間にも何回かやり直しているためである。

これで実行すると、たぶん値を100万個登録する例と同じく失敗するのだろうと予想していたのだが、意外なことに正常終了した。

実行時間は5分18秒801ミリ秒だそうだ。

実行後のファイルサイズは約147MBまで増大した。

登録したレジストリキーは下記のようになっている。

1,025個ずつサブキーを作るのに要した時間をグラフ化すると下記のようになる。

妙な動きだ。

まず、時々時間がかかっている部分を無視した場合、遅延する傾向があることは見て取れる。しかし絶対値としての遅延する度合いは、キーの下に値を登録する場合よりもは、ずっと影響が小さい。

最初の1,025個の作成に要する時間は100ms程度であるが、最後の1,025個の作成に要する時間は400ms程度である。

しかしこれだけでは、遅延する度合いがO(n)に比例するのか、O(logn)に比例するのかは判断がつかない。

それと、外れ値と思われる部分であるが、外部要因による撹乱だと解釈すると、回数が多いように思う。おそらくディスクI/Oが原因ではないかと考えているが、その根拠はない。

総論

とりあえず、以下のことがいえる。

  1. 単一のキーの下には、値を大量に登録してはならない。多分、1000個がいいところである。
  2. キーであれば、1個のキーの下に意外とたくさん登録しても大丈夫。しかし、やはり個数が多くなるのなら、キーを分割した方がよい。

最後に

最後にタイトル通り、レジストリを飽和させてみた。

開始前の状態で約147MBである。

念のためディスク容量も見ておく。

64.4GB中15.4GB使用しており、空き領域が47.9GBだという。

ここで、簡略化した以下のプログラムを走らせる。

open Microsoft.Win32;;
open System;;

// サブキーを開く
let key = Registry.CurrentUser.CreateSubKey( "Software\\test" )
 
// キーを書き込み続ける
Seq.iter
  ( fun n -> key.CreateSubKey( sprintf "test-%08X\n" n ).Close() )
  { 0 .. System.Int32.MaxValue }
 
// キーを閉じる
key.Close()

実行中はCPUを100%使い切って頑張っているようだ。

まぁ、1コアしか割り当てていないからなのだが。

以下は途中経過である。

NTUSER.DATも順調に肥え太ってきており、メモリの使用量も幾分増えていることが伺える。

その後、しばらく放置していたらファイルサイズがちょうど2GBに達したところで処理が異常終了した。

タスクマネージャで隠れているエラーメッセージは以下のようになっている。

NTUSER.DATのサイズは、まごうことなく2GB(2×1,024×1,024×1,024=2,147,483,648)になっている。

この後、レジストリの肥大に伴いメモリ不足が生じているのは明らかなので、とりあえずメモリの割り当てを増やして再起動してみた。

その後どういう動きになるのか確認して見ようと思ったのだが、まずそもそもログインできない。

この画面のまま、いつまでたっても使えるようにならない。一生懸命膨大な量のレジストリを読み込んでいるのか? あるいはレジストリに値を書き込むことができずに、何らかのエラーが発生しているのか? よくわからない。

この後1時間ぐらい放置していたが一向にログインできる様子がなかったから、やめてしまった。


連絡先 - サイトマップ - 更新履歴
Copyright (C)  2000 - 2016 nabiki_t All Rights Reserved.