ときどきの雑記帖″

最新ページへのリンク
目次ページへのリンク

一つ前へ 2014年2月(中旬)
一つ後へ 2014年3月(上旬)

ホームへ

2014年02月28日

■_

このマンガがすごい2014
一位だったからというのがあるんだろうけど 「さよならソルシエ」作者インタビューが載ってたのね。 このマンガがすごい! 2014
このマンガがすごい! 2014

お、なんか面白そうなものが 東京都・恵比寿で「AKIRA」などを手がけた大友克洋のポスター展 - 入場無料 | マイナビニュース 東京・六本木に日本初のアンディ・ウォーホル公式カフェ限定オープン | マイナビニュース

■_ GNU grep

総集編(中間的なまとめ)。 ちょっとどう進めたものか悩んでたもので。

GNU grep はマルチバイト文字を使う locale では(条件にもよるけれども)「遅い」 2.17 辺りでその辺を(部分的にでも)どうにかしようという変更が入った。

-i オプション指定時は、入力を「変換」してから検索を実行している (「一行ごとに処理」云々はたぶんこれが理由。 変換結果の格納のために新たにメモリ割り当てをしているので でかい「バッファ」を対象とすると面倒がある)。

文字を内部的に Unicode で持つということはしていないし、 Unicode が使われるという仮定もしていない (wchar_t は Unicode とは限らない)。 ただし、UTF-8 に対しては特別扱いしているところがある。 ほぼ毎回、必要になったところで wide character (≠Unicode) への変換やその逆の変換をいちいち行う。

たとえばある文字を小文字に変換する場合、 wide character に変換 → iswupper 関数で「大文字」か判定 → 「大文字」であれば towlower で「小文字」変換 → 変換された「小文字」を narrow character に変換 という手順を踏む。

正規表現エンジンを二種類抱えている (glibcと共通のものとそうでないもの)。 コンパイル時のオプションによっては pcre も抱えるが、それはとりあえず除外して話を進める。

2.17 → 2.18 での対応は「場当たり」 参考までに、2.17 と 2.18 のメインのプログラムの差分はこんなもん

diff -u grep-2.17/src/dfa.c grep-2.18/src/dfa.c
--- grep-2.17/src/dfa.c	2014-02-18 10:42:39.000000000 +0900
+++ grep-2.18/src/dfa.c	2014-02-21 15:23:40.000000000 +0900
@@ -753,7 +753,7 @@
 
 /* UTF-8 encoding allows some optimizations that we can't otherwise
    assume in a multibyte encoding.  */
-static inline int
+int
 using_utf8 (void)
 {
   static int utf8 = -1;
@@ -1108,28 +1108,31 @@
             {
               /* Defer to the system regex library about the meaning
                  of range expressions.  */
-              regex_t re;
-              char pattern[6] = { '[', 0, '-', 0, ']', 0 };
-              char subject[2] = { 0, 0 };
-              c1 = c;
-              if (case_fold)
-                {
-                  c1 = tolower (c1);
-                  c2 = tolower (c2);
-                }
-
-              pattern[1] = c1;
-              pattern[3] = c2;
-              regcomp (&re, pattern, REG_NOSUB);
-              for (c = 0; c < NOTCHAR; ++c)
+              struct re_pattern_buffer re = { 0 };
+              char const *compile_msg;
+#if 199901 <= __STDC_VERSION__
+              char pattern[] = { '[', '\\', c, '-', '\\', c2, ']' };
+#else
+              char pattern[] = { '[', '\\', 0, '-', '\\', 0, ']' };
+              pattern[2] = c;
+              pattern[5] = c2;
+#endif
+              re_set_syntax (syntax_bits | RE_BACKSLASH_ESCAPE_IN_LISTS);
+              compile_msg = re_compile_pattern (pattern, sizeof pattern, &re);
+              if (compile_msg)
+                dfaerror (compile_msg);
+              for (c = 0; c < NOTCHAR; c++)
                 {
-                  if ((case_fold && isupper (c)))
-                    continue;
-                  subject[0] = c;
-                  if (regexec (&re, subject, 0, NULL, 0) != REG_NOMATCH)
-                    setbit_case_fold_c (c, ccl);
+                  char subject = c;
+                  switch (re_match (&re, &subject, 1, 0, NULL))
+                    {
+                    case 1: setbit (c, ccl); break;
+                    case -1: break;
+                    default: xalloc_die ();
+                    }
                 }
               regfree (&re);
+              re_set_syntax (syntax_bits);
             }
 
           colon_warning_state |= 8;
diff -u grep-2.17/src/dfa.h grep-2.18/src/dfa.h
--- grep-2.17/src/dfa.h	2014-01-02 10:31:51.000000000 +0900
+++ grep-2.18/src/dfa.h	2014-02-21 15:23:40.000000000 +0900
@@ -99,3 +99,5 @@
    takes a single argument, a NUL-terminated string describing the error.
    The user must supply a dfaerror.  */
 extern _Noreturn void dfaerror (const char *);
+
+extern int using_utf8 (void);
diff -u grep-2.17/src/main.c grep-2.18/src/main.c
--- grep-2.17/src/main.c	2014-02-18 10:42:39.000000000 +0900
+++ grep-2.18/src/main.c	2014-02-21 15:23:40.000000000 +0900
@@ -34,6 +34,7 @@
 #include "c-ctype.h"
 #include "closeout.h"
 #include "colorize.h"
+#include "dfa.h"
 #include "error.h"
 #include "exclude.h"
 #include "exitfail.h"
@@ -1883,6 +1884,11 @@
 trivial_case_ignore (size_t len, char const *keys,
                      size_t *new_len, char **new_keys)
 {
+  /* Perform this translation only for UTF-8.  Otherwise, this would induce
+     a 100-200x performance penalty for non-UTF8 multibyte locales.  */
+  if ( ! using_utf8 ())
+    return false;
+
   /* FIXME: consider removing the following restriction:
      Reject if KEYS contain ASCII '\\' or '['.  */
   if (memchr (keys, '\\', len) || memchr (keys, '[', len))
diff -u grep-2.17/src/searchutils.c grep-2.18/src/searchutils.c
--- grep-2.17/src/searchutils.c	2014-02-10 14:18:37.000000000 +0900
+++ grep-2.18/src/searchutils.c	2014-02-21 15:23:40.000000000 +0900
@@ -19,6 +19,7 @@
 #include <config.h>
 #include <assert.h>
 #include "search.h"
+#include "dfa.h"
 #if HAVE_LANGINFO_CODESET
 # include <langinfo.h>
 #endif
@@ -234,13 +235,8 @@
   const char *p = *good;
   const char *prev = p;
   mbstate_t cur_state;
-#if HAVE_LANGINFO_CODESET
-  static int is_utf8 = -1;
-
-  if (is_utf8 == -1)
-    is_utf8 = STREQ (nl_langinfo (CODESET), "UTF-8");
 
-  if (is_utf8 && buf - p > MB_CUR_MAX)
+  if (using_utf8 () && buf - p > MB_CUR_MAX)
     {
       for (p = buf; buf - p > MB_CUR_MAX; p--)
         if (mbclen_cache[to_uchar (*p)] != (size_t) -1)
@@ -249,7 +245,6 @@
       if (buf - p == MB_CUR_MAX)
         p = buf;
     }
-#endif
 
   memset (&cur_state, 0, sizeof cur_state);
 

中間的なまとめといいつつこれまで書いてないことも書いてるけどまあいいやw

■_

■_

二月が逃げた

2014年02月27日

■_

健康診断。

Developer Reading List | Dr Dobb's

このふたつ面白かった。 Programming Exercises So Long, and Thanks for All the Tests

■_

今日も続けるよ! GNU grep 2.18リリース: 10倍速くなったと思ったら今度は200倍遅くなっていた | はむかず! git のコミット記録見てたら 2.18 てのがあったので、 てっきりタグかと思ったら違ってたw さて。

テストはja_JP.eucJPでやっている(日本語のこと気にしてくれてありがとう!) つまり流れとしては以下のとおり。 UTF8の場合、検索文字列を正規表現になおしてから検索したほうが10倍速くなったので、そのような変更をした(バージョン2.17としてリリース) ↓ 上記の変更でUTF8以外のとき、200倍遅くなっていた ↓ UTF8以外のマルチバイトのときは、正規表現変換はせずに、従来の方法で検索するように変更(バージョン2.18としてリリース) つまり、現在の実装としては、UTF8のときは検索文字列を正規表現に変換し、それ以外マルチバイトロケールの場合は テキストの方を変換してから検索することになっている。先日説明したトルコ語のI,i問題(あるいはトルコ語以外でも 言語によっては大文字小文字でバイト数が違う問題)は、UTF8以外では発生しないらしい。 文字コードの世界、深すぎる…

はてなブックマーク - GNU grep 2.18リリース: 10倍速くなったと思ったら今度は200倍遅くなっていた | はむかず! 今回もここについたコメント見ながらニヤニヤしてたんですがそれはさておき。 grep.git - grep を見るに

grep.git - grep

grep -i: avoid a performance regression in multibyte non-UTF8 locales

* src/main.c: Include dfa.h. (trivial_case_ignore): Perform this optimization only for UTF8 locales. This
rectifies a 100-200x performance regression in non-UTF8 multi-byte locales like ja_JP.eucJP. The regression
was introduced by the 10x UTF8/grep-i speedup, commit v2.16-4-g97318f5.

* NEWS (Bug fixes): Mention it. Reported by Norihiro Tanaka in http://debbugs.gnu.org/16232#50

repoted by タナカノリヒロ さんなので eucjp でのレポートなのもまあそういうことなのでは。 この方自身もいくつかコミットされてますね。 #16232 - [PATCH] grep: make --ignore-case (-i) faster (sometimes 10x) in multibyte locales - GNU bug report logs

#16232 - [PATCH] grep: make --ignore-case (-i) faster (sometimes 10x) in multibyte locales - GNU bug report logs

From: Norihiro Tanaka 
To: Padraig Brady 
Cc: 16232 <16232 <at> debbugs.gnu.org>, Jim Meyering 
Subject: Re: bug#16232: [PATCH] grep: make --ignore-case (-i) faster
 (sometimes 10x) in multibyte locales
Date: Wed, 19 Feb 2014 23:22:08 +0900

Hi,

Slow down may be caused by the patch, because MBCSET is processed by not
DFA engine but regexp engine.

I tested performance on grep-2.17 and the version which the patch is reverted.
Latter is 100x faster.

yes $(printf '%078dm' 0)|head -10000 > in

grep-2.17 original:
$ for i in $(seq 10); do env LC_ALL=ja_JP.eucJP time src/grep -i n in; done
Command exited with non-zero status 1
5.92user 1.69system 0:07.73elapsed 98%CPU (0avgtext+0avgdata 3856maxresident)k
0inputs+0outputs (0major+422minor)pagefaults 0swaps
Command exited with non-zero status 1
5.59user 1.87system 0:07.58elapsed 98%CPU (0avgtext+0avgdata 3872maxresident)k
0inputs+0outputs (0major+423minor)pagefaults 0swaps
Command exited with non-zero status 1
6.06user 1.58system 0:07.81elapsed 97%CPU (0avgtext+0avgdata 3872maxresident)k
0inputs+0outputs (0major+423minor)pagefaults 0swaps
Command exited with non-zero status 1
5.73user 1.66system 0:07.52elapsed 98%CPU (0avgtext+0avgdata 3856maxresident)k
0inputs+0outputs (0major+422minor)pagefaults 0swaps
Command exited with non-zero status 1
6.42user 1.19system 0:07.86elapsed 96%CPU (0avgtext+0avgdata 3872maxresident)k
0inputs+0outputs (0major+423minor)pagefaults 0swaps
Command exited with non-zero status 1
6.15user 1.56system 0:08.34elapsed 92%CPU (0avgtext+0avgdata 3888maxresident)k
0inputs+0outputs (0major+424minor)pagefaults 0swaps
Command exited with non-zero status 1
6.97user 0.61system 0:07.77elapsed 97%CPU (0avgtext+0avgdata 3856maxresident)k
0inputs+0outputs (0major+422minor)pagefaults 0swaps
Command exited with non-zero status 1
7.00user 0.57system 0:07.71elapsed 98%CPU (0avgtext+0avgdata 3872maxresident)k
0inputs+0outputs (0major+423minor)pagefaults 0swaps
Command exited with non-zero status 1
7.16user 0.25system 0:07.56elapsed 97%CPU (0avgtext+0avgdata 3872maxresident)k
0inputs+0outputs (0major+423minor)pagefaults 0swaps
Command exited with non-zero status 1
7.04user 0.39system 0:07.60elapsed 97%CPU (0avgtext+0avgdata 3856maxresident)k
0inputs+0outputs (0major+422minor)pagefaults 0swaps

After revert the patch:
$ for i in $(seq 10); do env LC_ALL=ja_JP.eucJP time src/grep -i n in; done
Command exited with non-zero status 1
0.07user 0.02system 0:00.10elapsed 92%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+232minor)pagefaults 0swaps
Command exited with non-zero status 1
0.03user 0.01system 0:00.05elapsed 101%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+218minor)pagefaults 0swaps
Command exited with non-zero status 1
0.04user 0.01system 0:00.06elapsed 90%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+218minor)pagefaults 0swaps
Command exited with non-zero status 1
0.03user 0.02system 0:00.05elapsed 103%CPU (0avgtext+0avgdata 3056maxresident)k
0inputs+0outputs (0major+217minor)pagefaults 0swaps
Command exited with non-zero status 1
0.04user 0.01system 0:00.06elapsed 86%CPU (0avgtext+0avgdata 3088maxresident)k
0inputs+0outputs (0major+219minor)pagefaults 0swaps
Command exited with non-zero status 1
0.04user 0.01system 0:00.06elapsed 91%CPU (0avgtext+0avgdata 3056maxresident)k
0inputs+0outputs (0major+217minor)pagefaults 0swaps
Command exited with non-zero status 1
0.04user 0.01system 0:00.06elapsed 87%CPU (0avgtext+0avgdata 3056maxresident)k
0inputs+0outputs (0major+217minor)pagefaults 0swaps
Command exited with non-zero status 1
0.03user 0.02system 0:00.05elapsed 105%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+218minor)pagefaults 0swaps
Command exited with non-zero status 1
0.04user 0.00system 0:00.06elapsed 90%CPU (0avgtext+0avgdata 3072maxresident)k
0inputs+0outputs (0major+218minor)pagefaults 0swaps
Command exited with non-zero status 1
0.04user 0.01system 0:00.06elapsed 90%CPU (0avgtext+0avgdata 3056maxresident)k
0inputs+0outputs (0major+217minor)pagefaults 0swaps

Norihiro

ここでの変更点を確認するとこう。


diff --git a/src/main.c b/src/main.c
index bd20297..56ec6b3 100644
--- a/src/main.c
+++ b/src/main.c
@@ -34,6 +34,7 @@
 #include "c-ctype.h"
 #include "closeout.h"
 #include "colorize.h"
+#include "dfa.h"
 #include "error.h"
 #include "exclude.h"
 #include "exitfail.h"
@@ -1883,6 +1884,11 @@ static bool
 trivial_case_ignore (size_t len, char const *keys,
                      size_t *new_len, char **new_keys)
 {
+  /* Perform this translation only for UTF-8.  Otherwise, this would induce
+     a 100-200x performance penalty for non-UTF8 multibyte locales.  */
+  if ( ! using_utf8 ())
+    return false;
+
   /* FIXME: consider removing the following restriction:
      Reject if KEYS contain ASCII '\\' or '['.  */
   if (memchr (keys, '\\', len) || memchr (keys, '[', len))
--
cgit v0.9.0.2

なんですが、ここだけ見ても本質を見落としてしまいます。 なぜこの trivial_case_ignore が utf8 のときだけしか効果がなく、 場合によってはむしろ逆効果になってしまうのか? それを理解するには、 例の十倍高速化パッチがどういったものなのかをきちんと把握しておく必要があるのです。 というわけでまだまだ続きます。

■_

2014年02月26日

■_

NHK のニュースでビットコイン関連のものが流れていましたが 「担保する」というのをアナウンサーが言ってたのを聴いて、 ああそこまで定着してんだなあと観念しました。

■_

続き。 今日は compile/execute 辺りを掘り進めようと思ったのですが、ちと方向転換。

いまさらgrepが10倍高速化したのはなぜか | はむかず!

・マルチバイトロケールによっては、「大文字→小文字」または「小文字→大文字」の変換をすると文字のバイト数が変わってしまうことがある(!)
・そのため、バッファを使った効率の良い検索が使えず、1行読み込んで変換して検索するという効率の悪い実装が行われていた
・そこで発想を変えて、検索される文字列を同値な正規表現にあらかじめ変換すると速くなった。つまり「foo」という文字列を検索するときは「[fF][oO][oO]」という正規表現に変換してから検索する。

これですが、「1行読み込んで変換」という部分は正しくありません。 一行分ずつ変換してはいるのですが、読み込みは一行ずつではありません。

昨日も見た部分ですが main.c の do_execute という関数の


do_execute (char const *buf, size_t size, size_t *match_size,
            char const *start_ptr)
{
(略)
  if (MB_CUR_MAX == 1 || !match_icase)
    return execute (buf, size, match_size, start_ptr);

というのが「効率のよい」方のやり方で、 一方「一行ずつ」というのは

  for (line_next = buf; line_next < buf + size; )
    {
      const char *line_buf = line_next;
      const char *line_end = memchr (line_buf, eolbyte,
                                     (buf + size) - line_buf);
      if (line_end == NULL)
        line_next = line_end = buf + size;
      else
        line_next = line_end + 1;

      if (start_ptr && start_ptr >= line_end)
        continue;

      result = execute (line_buf, line_next - line_buf, match_size, start_ptr);
      if (result != (size_t) -1)
        return (line_buf - buf) + result;
    }

のように、バッファ中の切れ目(改行コード)を探してから 一行の先頭と末尾を検索対象(の範囲)として渡しているのであって、 読み込み自体はどちらの方法でも変わりありません (fillbuf という関数が読み込みのメイン部分です)。

さて。

grep.c

static void
Gcompile (char const *pattern, size_t size)
{
  GEAcompile (pattern, size, RE_SYNTAX_GREP | RE_NO_EMPTY_RANGES);
}

static void
Ecompile (char const *pattern, size_t size)
{
  GEAcompile (pattern, size, RE_SYNTAX_POSIX_EGREP | RE_NO_EMPTY_RANGES);
}

static void
Acompile (char const *pattern, size_t size)
{
  GEAcompile (pattern, size, RE_SYNTAX_AWK);
}

static void
GAcompile (char const *pattern, size_t size)
{
  GEAcompile (pattern, size, RE_SYNTAX_GNU_AWK);
}

static void
PAcompile (char const *pattern, size_t size)
{
  GEAcompile (pattern, size, RE_SYNTAX_POSIX_AWK);
}

struct matcher const matchers[] = {
  { "grep",    Gcompile, EGexecute },
  { "egrep",   Ecompile, EGexecute },
  { "awk",     Acompile, EGexecute },
  { "gawk",    GAcompile, EGexecute },
  { "posixawk", PAcompile, EGexecute },
  { "fgrep",   Fcompile, Fexecute },
  { "perl",    Pcompile, Pexecute },
  { NULL, NULL, NULL },
};

面倒なので細かい部分は無慈悲に切り捨てて行きますが、 通常の grep として起動した場合「コンパイル」を行う関数は GEAcompile、 「実行」する関数は EGexecute です。 とはいえ今回関係するのは最後の部分の

dfasearch.c

void
GEAcompile (char const *pattern, size_t size, reg_syntax_t syntax_bits)
{

(略)

  dfa = dfaalloc ();
  dfacomp (pattern, size, dfa, 1);
  kwsmusts ();

  free(motif);
}

ここだけです。さらに言えば free() の手前にある kwsmusts() なのですが… ここまできて重大な事実が発覚!

dfasearch.c

/* If the DFA turns out to have some set of fixed strings one of
   which must occur in the match, then we build a kwset matcher
   to find those strings, and thus quickly filter out impossible
   matches. */
static void
kwsmusts (void)
{
  /* With case-insensitive matching in a multi-byte locale, do not
     use kwsearch, because in that case, it would be too expensive,
     requiring that we case-convert all searched input.  */
  if (MB_CUR_MAX > 1 && match_icase)
    return;

  struct dfamust const *dm = dfamusts (dfa);
  if (dm)
    {
      char const *err;
      kwsinit (&kwset);
      /* First, we compile in the substrings known to be exact
         matches.  The kwset matcher will return the index
         of the matching string that it chooses. */
      for (; dm; dm = dm->next)
        {
          if (!dm->exact)
            continue;
          ++kwset_exact_matches;
          if ((err = kwsincr (kwset, dm->must, strlen (dm->must))) != NULL)
            error (EXIT_TROUBLE, 0, "%s", err);
        }
      /* Now, we compile the substrings that will require
         the use of the regexp matcher.  */
      for (dm = dfamusts (dfa); dm; dm = dm->next)
        {
          if (dm->exact)
            continue;
          if ((err = kwsincr (kwset, dm->must, strlen (dm->must))) != NULL)
            error (EXIT_TROUBLE, 0, "%s", err);
        }
      if ((err = kwsprep (kwset)) != NULL)
        error (EXIT_TROUBLE, 0, "%s", err);
    }
}

今回注目している変更は主に grep.git - grep で行われた部分なのですがマルチバイト文字関連の高速化はこの他にも grep.git - grep があって、これで手の入った部分が最初の変更に影響してるっぽいのですね。 遅くしてしまったということはないと思いますが、 ある種無駄なことをしているんじゃないかという (たぶん、後の変更だけ入れておけば良いような気が。あーでも被ってない部分もあるな)。

そもそも、この kwsmusts() の先頭で MB_CUR_MAX > 1 && match_icase とかやってますけど trivial_case_ignore() で正規表現への置き換えが行われたときには match_icase って0になってんですよね。

で、commit 履歴を見ると

grep.git - grep

2014-01-26	dfasearch: skip kwset optimization when multi-byte+case-insensitive	Norihiro Tanaka	2	-38/+19
2014-01-26	dfa: remove GREP-ifdef'd code in favor of code used by gawk	Norihiro Tanaka	1	-8/+0
2014-01-25	gnulib: update to latest	Jim Meyering	1	-0/+0
2014-01-17	grep: DFA now uses rational ranges in unibyte locales	Paul Eggert	3	-28/+19
2014-01-17	grep: add undocumented '-X gawk' and '-X posixawk' options	Aharon Robbins	1	-0/+14
2014-01-11	tests: remove superfluous uses of printf	Pádraig Brady	1	-2/+2
2014-01-10	grep: make --ignore-case (-i) faster (sometimes 10x) in multibyte locales	Jim Meyering	4	-1/+160

正規表現への置き換えをやってる方は 1月10日に行われたもので、 それとは別の方が行った高速化が1月26日にあります。 前の変更が反映されてないところに変更をかけたとも思えませんが、どうにも引っかかるものが。 というのは、1月10日で避けるようになっていた「重い」部分が 1月26日の変更で書きかえられてるようなんですね。 ということは、正規表現への置き換えという手法による高速化の影響を確認するには 2.17 のソースコード(と1月10日の差分)を見てはいけなかったのでしたーーーーーっ。

(たぶん)続く。

■_

History of Java Language « The Crazy Programmer

History of Java Language « The Crazy Programmer

There were five primary goals for creating the Java language

1. It should be "simple, object-oriented and familiar"
2. It should be "robust and secure"
3. It should be "architecture-neutral and portable"
4. It should execute with "high performance"
5. It should be "interpreted, threaded, and dynamic"

■_

2014年02月25日

■_

無気力ー

■_ GNU grep

いまさらgrepが10倍高速化したのはなぜか | はむかず!


1. マルチバイトロケールによっては、「大文字→小文字」または「小文字→大文字」の変換をすると文字のバイト数が変わってしまうことがある(!)

2. そのため、バッファを使った効率の良い検索が使えず、1行読み込んで変換して検索するという効率の悪い実装が行われていた

3. そこで発想を変えて、検索される文字列を同値な正規表現にあらかじめ変換すると速くなった。つまり「foo」という文字列を検索するときは「[fF][oO][oO]」という正規表現に変換してから検索する。

という結論に違和感。 1. については良いのだけど、 なぜ、バイト数が変わるケースがあると「効率の良い検索」ができないのか? (「そのため、」というのはそういうことですよね) そして、行ごとに処理することでなぜそんなに効率が落ちるのか? (三つのなかで遅くなる要因に言及しているのはこれだけ) 最後に、なぜ正規表現に変換すると速くなるのか。 これはまあ2. で使えなくなった「効率の良い検索」が使えるようになったということなのだろうけど それはなぜ? (「正規表現に変換したから」というのは理由になっていない)

まず、一行ごと云々に関して

main.c 

/* Invoke the matcher, EXECUTE, on buffer BUF of SIZE bytes.  If there
   is no match, return (size_t) -1.  Otherwise, set *MATCH_SIZE to the
   length of the match and return the offset of the start of the match.  */
static size_t
do_execute (char const *buf, size_t size, size_t *match_size,
            char const *start_ptr)
{
  size_t result;
  const char *line_next;

  /* With the current implementation, using --ignore-case with a multi-byte
     character set is very inefficient when applied to a large buffer
     containing many matches.  We can avoid much of the wasted effort
     by matching line-by-line.

     FIXME: this is just an ugly workaround, and it doesn't really
     belong here.  Also, PCRE is always using this same per-line
     matching algorithm.  Either we fix -i, or we should refactor
     this code---for example, we could add another function pointer
     to struct matcher to split the buffer passed to execute.  It would
     perform the memchr if line-by-line matching is necessary, or just
     return buf + size otherwise.  */
  if (MB_CUR_MAX == 1 || !match_icase)
    return execute (buf, size, match_size, start_ptr);

  for (line_next = buf; line_next < buf + size; )
    {
      const char *line_buf = line_next;
      const char *line_end = memchr (line_buf, eolbyte,
                                     (buf + size) - line_buf);
      if (line_end == NULL)
        line_next = line_end = buf + size;
      else
        line_next = line_end + 1;

      if (start_ptr && start_ptr >= line_end)
        continue;

      result = execute (line_buf, line_next - line_buf, match_size, start_ptr);
      if (result != (size_t) -1)
        return (line_buf - buf) + result;
    }

  return (size_t) -1;
}

MB_CUR_MAX が1でなく、また、大小文字無視の検索(match_icase が0以外)場合に execute() の呼び出しが一行ごとになっているのが見て取れます。 中身はともかくとして、execute() の呼び出し回数の増加分だけ オーバーヘッドも増えるだろうことが予想できます。 が、なぜこのような使い分けが必要なのかはわかりません。

do_execute() がどこで呼ばれているかというとここ。 名前からしてバッファに対してgrepするというところですか

/* Scan the specified portion of the buffer, matching lines (or
   between matching lines if OUT_INVERT is true).  Return a count of
   lines printed. */
static intmax_t
grepbuf (char const *beg, char const *lim)
{
  intmax_t nlines, n;
  char const *p;
  size_t match_offset;
  size_t match_size;

  nlines = 0;
  p = beg;
  while ((match_offset = do_execute (p, lim - p, &match_size,
                                     NULL)) != (size_t) -1)
    {
      char const *b = p + match_offset;
      char const *endp = b + match_size;
      /* Avoid matching the empty line at the end of the buffer. */
      if (b == lim)
        break;
      if (!out_invert)
        {
          prtext (b, endp, NULL);
          nlines++;
          outleft--;
          if (!outleft || done_on_match)
            {
              if (exit_on_match)
                exit (EXIT_SUCCESS);
              after_last_match = bufoffset - (buflim - endp);
              return nlines;
            }
        }
      else if (p < b)
        {
          prtext (p, b, &n);
          nlines += n;
          outleft -= n;
          if (!outleft)
            return nlines;
        }
      p = endp;
    }
  if (out_invert && p < lim)
    {
      prtext (p, lim, &n);
      nlines += n;
      outleft -= n;
    }
  return nlines;
}

さらに呼び出し階層の上に行くとファイルに対する grep な関数に なっていくので省略

次に目先を変えて main() のオプション解析のあたり

int
main (int argc, char **argv)
{

      case 'i':
      case 'y':			/* For old-timers . . . */
        match_icase = 1;
        break;

-i オプション指定時には match_icase という変数に1をセットと。

そこからちょっと進むと

  /* As currently implemented, case-insensitive matching is expensive in
     multi-byte locales because of a few outlier locales in which some
     characters change size when converted to upper or lower case.  To
     accommodate those, we revert to searching the input one line at a
     time, rather than using the much more efficient buffer search.
     However, if we have a regular expression, /foo/i, we can convert
     it to an equivalent case-insensitive /[fF][oO][oO]/, and thus
     avoid the expensive read-and-process-a-line-at-a-time requirement.
     Optimize-away the "-i" option, when possible, converting each
     candidate alpha, C, in the regexp to [Cc].  */
  if (match_icase)
    {
      size_t new_keycc;
      char *new_keys;
      /* It is not possible with -F, not useful with -P (pcre) and there is no
         point when there is no regexp.  It also depends on which constructs
         appear in the regexp.  See trivial_case_ignore for those details.  */
      if (keycc
          && ! (matcher
                && (STREQ (matcher, "fgrep") || STREQ (matcher, "pcre")))
          && trivial_case_ignore (keycc, keys, &new_keycc, &new_keys))
        {
          match_icase = 0;
          free (keys);
          keys = new_keys;
          keycc = new_keycc;
        }
    }

#if MBS_SUPPORT
  if (MB_CUR_MAX > 1)
    build_mbclen_cache ();
#endif

  compile (keys, keycc);
  free (keys);

match_icase が0以外のときに、 さらに keycc があって、matcher が fgrep でも pcre でもないときに trivial_case_ignore を呼び出してその戻り値が0以外のときに match_icase を 0 にしています。 つまり、大小文字の違いを無視するという指定を無効にしているわけですね。 ということは、名前からしても trivial_case_ignore がアヤシイ。


#define MBRTOWC(pwc, s, n, ps) \
  (MB_CUR_MAX == 1 ? \
   (*(pwc) = btowc (*(unsigned char *) (s)), 1) : \
   mbrtowc ((pwc), (s), (n), (ps)))

#define WCRTOMB(s, wc, ps) \
  (MB_CUR_MAX == 1 ? \
   (*(s) = wctob ((wint_t) (wc)), 1) : \
   wcrtomb ((s), (wc), (ps)))

/* If the newline-separated regular expressions, KEYS (with length, LEN
   and no trailing NUL byte), are amenable to transformation into
   otherwise equivalent case-ignoring ones, perform the transformation,
   put the result into malloc'd memory, *NEW_KEYS with length *NEW_LEN,
   and return true.  Otherwise, return false.  */
static bool
trivial_case_ignore (size_t len, char const *keys,
                     size_t *new_len, char **new_keys)
{
  /* FIXME: consider removing the following restriction:
     Reject if KEYS contain ASCII '\\' or '['.  */
  if (memchr (keys, '\\', len) || memchr (keys, '[', len))
    return false;

  /* Worst case is that each byte B of KEYS is ASCII alphabetic and each
     other_case(B) character, C, occupies MB_CUR_MAX bytes, so each B
     maps to [BC], which requires MB_CUR_MAX + 3 bytes.   */
  *new_keys = xnmalloc (MB_CUR_MAX + 3, len + 1);
  char *p = *new_keys;

  mbstate_t mb_state;
  memset (&mb_state, 0, sizeof mb_state);
  while (len)
    {
      wchar_t wc;
      int n = MBRTOWC (&wc, keys, len, &mb_state);

      /* For an invalid, incomplete or L'\0', skip this optimization.  */
      if (n <= 0)
        {
        skip_case_ignore_optimization:
          free (*new_keys);
          return false;
        }

      char const *orig = keys;
      keys += n;
      len -= n;

      if (!iswalpha (wc))
        {
          memcpy (p, orig, n);
          p += n;
        }
      else
        {
          *p++ = '[';
          memcpy (p, orig, n);
          p += n;

          wchar_t wc2 = iswupper (wc) ? towlower (wc) : towupper (wc);
          char buf[MB_CUR_MAX];
          int n2 = WCRTOMB (buf, wc2, &mb_state);
          if (n2 <= 0)
            goto skip_case_ignore_optimization;
          assert (n2 <= MB_CUR_MAX);
          memcpy (p, buf, n2);
          p += n2;

          *p++ = ']';
        }
    }

  *new_len = p - *new_keys;

  return true;
}

ここで foobar → [Ff][Oo][Oo][Bb][Aa][Rr] への変換をやってました。

さて、では続いて正規表現マッチングのところを… と思いましたが時間切れのようです。

あ、あとあれだ。今回見ているこれはそもそも

Savannah Git Hosting - grep.git/commitdiff

These days, nearly everyone uses a multibyte locale, and grep is often
used with the --ignore-case (-i) option, but that option imposes a very
high cost in order to handle some unusual cases in just a few multibyte
locales.  This change gets most of the performance of using LC_ALL=C
without eliminating the ability to search for multibyte strings.

With the following example, I see an 11x speed-up with a 2.3GHz i7:
Generate a 10M-line file, with each line consisting of 40 'j's:

    yes jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj | head -10000000 > k

Time searching it for the simple/noexistent string "foobar",
first with this patch (best-of-5 trials):

    LC_ALL=en_US.UTF-8 env time src/grep -i foobar k
        1.10 real         1.03 user         0.07 sys

Back out that commit (temporarily), recompile, and rerun the experiment:

    git log -1 -p|patch -R -p1; make
    LC_ALL=en_US.UTF-8 env time src/grep -i foobar k
        12.50 real        12.41 user         0.08 sys

元がとんでもなく遅いものだったというのをお忘れなく。 この先続けるかどうかは気分次第。

■_

2014年02月24日

■_

例のアレでようやくiOS7に上げることにした…… なんじゃこりゃーーっ

あれ Bletchley ParkとTMNOCとの大きな問題 の元記事読んだ覚えがない… The Colossal Problem with Bletchley Park and TMNOC

■_

■_

iOS7.0.6で修正された「最悪のセキュリティバグ」はありがちなコーディングミスで発生していた | スラッシュドット・ジャパン アップル で今日も結構盛り上がっていたようで。 あ、こりゃ元記事読まないで思い込みで言ってるなという 発言(ブクマコメントとかツイートとか)を見る度にやにやしてしまう 性悪なわたしでありました。

もちろんそうでないものもあるわけで

なんかハッシュタグも決まってた Twitter / 検索 - #gotofail

またうたた寝して時間がなくなったのであとで書くモード Learning from Apple’s #gotofail Security Bug | Arie van Deursen

■_

例のトヨタの件で何か進展がと思ったらちょっと違ったぽい。 slashdot で取りあげられたサイト見に行ったけど… Stack Overflow Could Explain Toyota Vehicles' Unintended Acceleration - Slashdot ところで slashdotのこれをうけた reddit のこれにはw Stack Overflow Could Explain Toyota Unintended Acceleration : programming

■_

Fun with Unicode and Julia - IainDunning.com Efficient Aggregates in Julia | Hacker News

■_

2014年02月23日

■_

eneloop が1本寿命尽きたっぽい。 充電器にかけてもすぐに充電完了になってしまうけど 使おうとしてもダメ。 まー結構長く使ったからなあ。こんなもんか。

情報処理技術者試験
春の試験の申し込みは締め切られたんでしたっけか。 前にちょっと書いたと思うんですが、某弊社、昨今のソフトウェアの重要性の増大に鑑みて 情報処理技術者試験の受験(と合格)を奨励するんだそうですよ。 それで、そのために何をやるか(やったか)というと、 「合格者に対する受験料を会社が負担」 合格して会社に対して申請したら受験料分を出してくれるんですと。 あー、応用情報以上だったかな。まあいいやその辺は。 もうね(以下検閲削除)

秋葉原
久しぶりに、 中央通りからJRと反対側に一本向こうの通り(名前ついてたっけ?)を 末広町の方まで幾つか店に(パーツショップな)入りながら歩いてみたりしたんですが あそこもどうしてだいぶ雰囲気変わってますねー。 んで、帰りは歩行者天国の中央通りで秋葉原駅の方へ。 そういやこれを見かけました。あんなところにこういうのがあったのですねー 秋葉原の「花房神社」が、中央通りからも見えるように: Seaside Tears 2

二巻が四月予定と。めもめも。 スティーブ・ジョブズ(2)/ヤマザキマリ【コミック】 まんが王倶楽部 MangaohClub

■_ 脆弱性

なんかとある機械のファームにバグがあり WRT120N fprintf Stack Overflow - /dev/ttyS0

WRT120N fprintf Stack Overflow - /dev/ttyS0

With a good firmware disassembly and JTAG debug access to the WRT120N, it’s time to start examining the code
for more interesting bugs.

As we’ve seen previously, the WRT120N runs a Real Time Operating System. For security, the RTOS’s
administrative web interface employs HTTP Basic authentication:

WRT120N fprintf Stack Overflow - /dev/ttyS0

The interesting bit of code to focus on is this:
	
  fprintf(request->socket, "Location %s\n\n", GetWebParam(cgi_handle, "TM_Block_URL"));

Although it at first appears benign, cgi_tmUnBlock‘s processing of the TM_Block_URL POST parameter is
exploitable, thanks to a flaw in the fprintf implementation: 

長い文字列渡すとどかーんというありがちなパターンでした。と。 元記事は画像をたくさん使って詳しく説明してて面白いですよ

■_ goto

バグの話その2 午前中に Twitter / anohana: うわぁ。S式を使ってたらこんな間違いは無かったろうに… ht ... このツイートで知ったのですが ImperialViolet - Apple's SSL/TLS bug

いやまあそこかしこで盛り上がってること Apple's SSL/TLS bug | Hacker News アップル iOS 7.0.6提供開始、SSLの脆弱性を修正。OS X にも同じバグ発覚 - Engadget Japanese Apple's SSL/TLS bug : programming Use of GOTOs and implicit If statement blocks that led to server SSL certs being ignored in IOS 10.9+ : programming Learning from Apple’s #gotofail Security Bug | Arie van Deursen Apple史上最悪のセキュリティバグか、iOSとOS XのSSL接続に危険すぎる脆弱性が発覚──原因はタイプミス? | アプリオ

こんなご意見が

■_

■_

今の自分を拾ってくれる会社が5社あるか?―リブセンス取締役 桂大介のキャリア論[1]│CAREER HACK うーむ…

2014年02月22日

■_

梅が咲いてまして。 梅と言えば昨年引っ越された近所の家に梅の木があって、 行き帰りに目にしていたのですが引っ越しの後建物を取り壊すときにその木もなくなってしまいました。 その土地はしばらく更地だったのですが最近になって建築のお知らせの看板が。 年年歳歳花相似 歳歳年年人不同。 オチはありません。

InfoQ のたぶん日本語翻訳はされないよなーってのにこういうのがありまして Timothy Baldridge on Clojure's Core.Async その sammary のところに Timothy Baldridge (@timbaldridge) is a developer with Cognitect Inc. He hails from the mountain regions of Denver Colorado (USA). He is a polyglot programmer with experience in Clojure, C#, Python, and Erlang. Most recently he was deeply involved in the development of Clojure's Core.Async library, where he designed, implemented, and maintains the state-machine code rewriting macro known as "go". polyglot programmer (複数のプログラミング言語を使うプログラマー) って最近 それなりに見かけるような気がするなあ。そういや polyなんとかって色々あるよね、で思いつくままに挙げてみると ポリバレント (polyvalent - Wiktionary) ポリマー (polymerの意味 - 英和辞典 Weblio辞書) polyline (Polyline クラス (System.Windows.Shapes)) polynomial (expression) (多項式の英語・英訳 - 英和辞典・和英辞典 Weblio辞書) ポリフェノール - Wikipedia ポリエチレン - Wikipedia ポリエステル - Wikipedia ポリカーボネート - Wikipedia ポリプロピレン - Wikipedia ポリウレタン - Wikipedia あ、ポリゴンもか ポリゴン - Wikipedia まあ poly- の意味がそういうものなので polyの意味 - 英和辞典 Weblio辞書

おまけ。 OVA 新 破裏拳ポリマー [DVD]
OVA 新 破裏拳ポリマー [DVD]

■_ QCon

QCon New York 2014のトラック発表 を眺めてると Architectures You've Always Wondered About Applied Data Science and Machine Learning Java Innovations Software Architecture Improvements 辺りを見たいかなあ。 Architectures you've always wondered about | QCon New York 2014 Java Innovations - The Latest Trends in Java Technology | QCon New York 2014 Software Architecture Improvements | QCon New York 2014

■_

■_

technical debt ってちょっと書こうかと思ったんだけど 今ひとつまとまりが。

2014年02月21日

■_

Flash Player更新版がまた緊急公開、ゼロデイ攻撃発生のため - ITmedia エンタープライズ なんでこう後から後から(ry なのか、ソースコードを見てみたい。

日本語記事が出るとぱっと広まるのに今回ももやもや GoogleのJavaコーディング規約 といいつつこっちの元記事は見落としてたな。 QCon New York 2014のトラック発表 ま、行けないんですけどね。

アキバ系!電脳空間カウボーイズZ: 第三百七十二回 橋本和幸x清水亮「関数言語ナイト!幻のLISPマシンSymbolics!」前編

■_ Why I prefer Java over C++

意見はイロイロだよね。 と思いつつ読んでみたらあれあれ? という気が。 事実誤認というか思い込み、単に知らないで書いちゃってるところもないだろうか

Why I prefer Java over C++ | softwarecave

Why I prefer Java over C++
Posted on February 18, 2014	by robertp1984

C++ is a language I used for almost 2 years at work, for all my university projects except when there was an
explicit requirement to use a different programming language and at home for very different tasks. The
language alone is really powerful and well designed although the designers had to keep it compatible with C.
Like every programming language, C++ has its own difficulties and problems:

(略)

Don’t get me wrong – I really like C++. I got used to the issues mentioned above and they are not big
impediments for me. They just slow me down. I like pointers (the references in Java are very similar concept),
I have no bad experience with multiple inheritance and I like templates. I fully understand why the handling
of programming errors is not much better, why the compilation is slow and why memory management is done this
way.

But there is still one and the biggest reason why I don’t want to use C++ at work: very poor libraries. Of
course, you can say that we have famous STL and Boost in C++ but to me they are just a bunch of weird macros,
even weirder templates and unnecessary inheritance. Just ask yourself whether you can easily understand how to 
use given Boost class by just looking at its header and how hard is to understand the compilation errors when
using them wrong.

(略)

挙げられた項目は ポインター、多重継承、テンプレート、エラー処理、コンパイル速度、メモリー管理 なんだけどんー。

ライセンス云々に至っては、はあそうですか。としか。

■_

PLMニュース:「IoTの経済効果、3Dプリンティングの約10倍」――PTCにおけるIoT戦略とは - MONOist(モノイスト) とかありますが、こういう話もあると モノのインターネットはすさまじく危険だ - そして多くはパッチ不可能である by Bruce Schneier (The Internet of Things Is Wildly Insecure - And Often Unpatchable) 元記事読んで気になってたんだけど 翻訳されてたのね(しかも新山さんだー)

Internet of Everythingの衝撃 (Ciscoシリーズ(NextPublishing)) Internet of Everythingの衝撃 IoT/M2M基盤上で人・モノ・データ・プロセスがつながる (Ciscoシリーズ(NextPublishing))

Amazon.co.jp: Rethinking the Internet of Things: A Scalable Approach to Connecting Everything eBook: Francis daCosta: Kindleストア あら kindle版のお値段が 紙の本の価格: ¥ 4,167 Kindle 価格: ¥ 0

■_


一つ前へ 2014年2月(中旬)
一つ後へ 2014年3月(上旬)

ホームへ


リンクはご自由にどうぞ

メールの宛先はこちらkbk AT kt DOT rim DOT or DOT jp