2016/08/17


SSEを使ってHTMLエスケープを高速化してみた - k0kubun's blog

高速なHTMLエスケープをするライブラリを作った ある日HTMLエスケープを速くしたくなって、hescapeというライブラリを作った。 github.com とにかく速いHTMLエスケープがしたい R...

http://k0kubun.hatenablog.com/entry/hescape

以前、moznion 氏の petit-html-escaper を勝手に高速化した時の話。

GitHub - moznion/petit-html-escaper: A simple and small escaper for HTML with SSE4.2 function

Author moznion ( moznion@gmail.com ) mattn License The MIT License (MIT) Copyright © 2015 moznion, h...

https://github.com/moznion/petit-html-escaper

速くしたと言っても SSE 部分ではない。ベンチマークの比較元である非 SSE な実装の方。元のベンチマーク結果が README.md に残っている。

petit-html-escaper: 3.935205 [sec]
simple-impl: 5.634651 [sec]

petit-html-escaper is faster 143.185715% than simple implementation

以下がその時の比較元のコード。思いつくままに実装された単純なコードです。

static void simple_escape_html(char *dst, const char *input, size_t input_size) {
  for (int i = 0; i < input_size; i++) {
    const char c = *(input++);
    switch (c) {
      case '&':
        memcpy(dst, "&amp;"5);
        dst += 5;
        break;
      case '>':
        memcpy(dst, "&gt;"4);
        dst += 4;
        break;
      case '<':
        memcpy(dst, "&lt;"4);
        dst += 4;
        break;
      case '"':
        memcpy(dst, "&quot;"6);
        dst += 6;
        break;
      case '\'':
        memcpy(dst, "&#39;"5);
        dst += 5;
        break;
      case '`':
        // For IE. IE interprets back-quote as valid quoting characters
        // ref: https://rt.cpan.org/Public/Bug/Display.html?id=84971
        memcpy(dst, "&#96;"5);
        dst += 5;
        break;
      case '{':
        // For javascript templates (e.g. AngularJS and such javascript frameworks)
        // ref: https://github.com/angular/angular.js/issues/5601
        memcpy(dst, "&#123;"6);
        dst += 6;
        break;
      case '}':
        // For javascript templates (e.g. AngularJS and such javascript frameworks)
        // ref: https://github.com/angular/angular.js/issues/5601
        memcpy(dst, "&#125;"6);
        dst += 6;
        break;
      default:
        memcpy(dst, &c, 1);
        dst += 1;
    }
  }
  *dst++ = *"\0";
}

ソースも見渡しが良いし悪いコードではないと思いましたが、SSE だから速くなるという言葉にカッとして勢いだけでこの実装を直してみました。そのコードが以下。

static void simple_escape_html(char *dst, const char *input, size_t input_size) {
  const char *ptr = input, *end = input + input_size;
  const static int pp[UCHAR_MAX+1] = {
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    0,0,4,0,0,0,1,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,0,2,0,
    0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    6,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,8,0,0
    /* following zero(s) */
  };
  const static char* dd[] = {NULL"&amp;""&gt;""&lt;""&quot;""&#39;""&#96;""&#123;""&#125;"};
  const static int dl[] = {054465566};
#define _ESC_AND_COPY(d,s,n) { memcpy(d,s,n); d += n; }
  while (ptr < end) {
    unsigned char c = *ptr++;
    int i = pp[c];
    if (i == 0) *dst++ = c;
    else _ESC_AND_COPY(dst, dd[i], dl[i]);
  }
#undef _ESC_AND_COPY
  *dst++ = 0;
}

memcpy を相手にしても結果は変わらないので、処理分岐を相手にした。switch 文は遅くなるので文字の ASCII コードをインデックスに使い、置換すべき文字列とオフセットを得る処理です。

ベンチマークがどの様に変わったかというと。。。

petit-html-escaper: 4.954000 [sec]
simple-impl: 4.323000 [sec]
petit-html-escaper is faster 87.262817% than simple implementation

なんと非 SSE の方が速くなってしまいました。もちろん入力データによっては SSE を使って1命令で処理できるバイト数を増やす事は出来るのですが、switch 文や if 文を削るだけでも十分 SSE と戦えるという結果を出せました。現在このコードは moznion 氏の petit-html-escaper の比較元コードとしてマージされています。一部 macOSX で使われている CPU アーキテクチャでは若干 simple_escape_html の方が遅くなるケースがある様ですが、僕が調べた限り Windows/Linux では同程度か、このコードの方が速いという結果が得られました。

SSE に頼るのも良いけど、カリカリチューニングでもまだまだやれるんですよ。皆さん。

Posted at by



2016/05/30


C言語とか C++ でマイクロフレームワークとか聞くと鼓動の高鳴りを抑えられなくなるmattnですこんにちわ。

Balde — A microframework for C based on GLib and bad intentions.

Features Simple templating engine, that converts markup to C code that is linked directly to the app...

https://balde.rgm.io/

2016年に glib で Web だと?と言われそうな気がしなくないですが面白そうな物を見つけました。特徴は

  • 簡単なテンプレートエンジンを提供し、マークアップからC言語のコードへ変換されアプリケーションのバイナリに直接埋め込まれる。
  • 静的リソースはアプリケーションのバイナリに埋め込まれサーブされる。
  • RESTful なリクエストのディスパッチを行い、ほぼ全ての HTTP メソッドをサポート。
  • URL の逆引き。 (Flask の "url_for" 関数に似ている)
  • セキュアクッキー(クライアントサイドセッション)
  • ファイルのアップロード
  • SCGI や CGI サーバとの親和性
  • GLib が稼働するあらゆるシステムで稼働する

まずは簡単なアプリケーションから。

#include <balde.h>

balde_response_t*
hello(balde_app_t *app, balde_request_t *request) {
  return balde_make_response("Hello World! I'm the balde! :D");
}

int
main(int argc, char *argv[]) {
  balde_app_t *app = balde_app_init();
  balde_app_add_url_rule(app, "hello""/", BALDE_HTTP_GET, hello);
  balde_app_run(app, argc, argv);
  balde_app_free(app);
  return 0;
}

pkg-config が提供されるのでコンパイルも楽ちん。

$ gcc -o app `pkg-config --cflags balde` main.c `pkg-config --libs balde`

静的ファイルを扱うには少し癖があり、定義ファイルからリソースとしてコンパイルする必要があります。

<?xml version="1.0" encoding="UTF-8"?>
<gresources>
    <gresource prefix="/static">
        <file>foo.js</file>
        <file>foo.css</file>
        <file>asd/bola.txt</file>
    </gresource>
</gresources>

もちろんこの XML と同様に static/foo.js, static/foo.css, static/asd/bola.txt が置かれているとします。リソースの生成方法は以下の通り。

$ glib-compile-resources --generate --sourcedir static --target static-resources.c static-resources.xml

ソースファイルは以下の様にルーティングを変更します。

#include <balde.h>
#include "static-resources.h"

balde_response_t*
hello(balde_app_t *app, balde_request_t *request) {
  return balde_make_response("Hello World! I'm the balde! :D");
}

int
main(int argc, char *argv[]) {
  balde_app_t *app = balde_app_init();
  balde_resources_load(app, static_resources_get_resource());
  balde_app_add_url_rule(app, "hello""/", BALDE_HTTP_GET, hello);
  balde_app_run(app, argc, argv);
  balde_app_free(app);
  return 0;
}

そしてコンパイル

$ gcc -o app `pkg-config --cflags balde` main.c static-resources.c `pkg-config --libs balde`

テンプレートを使う場合は専用ツールを利用します。templates/hello.html というファイルを用意します。

<h1>Hello, {{ name }}!</h1>

コマンドラインから balde-template-gen コマンドを実行します。

$ balde-template-gen templates/hello.html templates/hello.h
$ balde-template-gen templates/hello.html templates/hello.c

ソースコードを修正します。

#include <balde.h>
#include "static-resources.h"
#include "templates/hello.h"

balde_response_t*
hello(balde_app_t *app, balde_request_t *request) {
  balde_response_t *response = balde_make_response("");
  const gchar *name = balde_request_get_arg(request, "name");
  balde_response_set_tmpl_var(response, "name", name != NULL ? name : "World");
  balde_template_hello(app, request, response);
  return response;
}

int
main(int argc, char *argv[]) {
  balde_app_t *app = balde_app_init();
  balde_resources_load(app, static_resources_get_resource());
  balde_app_add_url_rule(app, "hello""/", BALDE_HTTP_GET, hello);
  balde_app_run(app, argc, argv);
  balde_app_free(app);
  return 0;
}

そしてコンパイル。

$ gcc -o app `pkg-config --cflags balde` main.c static-resources.c templates/hello.c `pkg-config --libs balde`

出来上がったアプリケーションに -s オプションを付けるとスタンドアローンサーバとして起動します。ブラウザから http://localhost:8080/?name=mattn の様なURLを開くとちゃんと動作しているのが分かるかと思います。尚、msys2 でビルドして見ましたが問題なく動作しています。glib がインストールされている同じOS同士ならばバイナリ1つでデプロイが完了します。便利そうですね。

LGPL 2.1 のライセンスのもと利用出来ます。

Posted at by



2016/03/28


Windows で C++ を使い、データベースの接続先を選ばないアプリケーションを書くのであれば ODBC 接続が一つの選択肢になります。しかし ODBC 接続のアプリケーションを書くのは非常に骨の折れる仕事です。

データの取得 - eternalwindows.jp
http://eternalwindows.jp/windevelop/odbc/odbc05.html

SQLAllocHandle によるハンドルの作成を事ある毎に行わなければなりません。そんな ODBC を使ったアプリケーションの開発を楽にしてくれそうなのが nanodbc です。

nanodbc

A small C++ wrapper for the native C ODBC API.

https://lexicalunit.github.io/nanodbc/

Windows で ODBC のシステムDSNに「mattn」という名前の接続情報を作り、今回は SQLite3 の ODBC ドライバを割り当てました。

odbc

コードも SQLAllocHandle が頻出しないのでとても綺麗に掛けます。

#include <nanodbc.h>
#include <algorithm>
#include <cstring>
#include <iostream>

using namespace std;

int
main(int argc, char* argv[]) {
  try {
    nanodbc::connection conn("DSN=mattn");
    cout << "Connected with driver " << conn.driver_name() << endl;
    execute(conn, "drop table if exists example;");
    execute(conn, "create table example(id integer primary key, content text);");

    nanodbc::statement stmt(conn);
    prepare(stmt, "insert into example(content) values(?);");
    stmt.bind(0"ふー");
    nanodbc::result result = execute(stmt);
    cout << "\nAffected " << result.affected_rows() << " rows";

    nanodbc::result rows = execute(conn, "select * from example;");
    cout << "\nDisplaying " << rows.affected_rows() << " rows ";
    cout << "(" << rows.rowset_size() << " fetched at a time):" << endl;
    const short cols = rows.columns();
    for(short i = 0; i < cols; i++)
      cout << rows.column_name(i) << "\t";
    cout << endl;

    while(rows.next()) {
      for(short i = 0; i < cols; i++)
        cout << "(" << rows.get<string>(i, "null") << ")\t";
      cout << endl;
    }
  } catch(const exception& e) {
    cerr << e.what() << endl;
    return 1;
  }
}

/* vim:set et sw=2 cino=j2: */

ただしあまり C++ ぽさが無いので物足りない人もいるかもしれません。ライセンスは MIT です。

Posted at by