URLエンコード/デコード(比較にC++とclojure追加)

昨日の続き。
比較対象にC++clojureを追加し、Javaのコードも若干変更した。

下の三つが、それぞれのベンチマーク用のコード。(ベンチマーク用データは、前回と同様のものを使用する)


参照: mmap_t

/** C++ **/
////////////////////////////
// ファイル名: url_encode.cc
// g++ -O2 -ourl_encode url_encode.cc
// time url_encode 対象ファイル
#include <string>
#include "mmap_t.h"

bool is_safe_char(char c) {  return isalnum(c)||c=='.'||c=='-'||c=='_'||c=='*'; }

const char* encode_char_to_hex(char c, char* buf) {
  buf[1]="0123456789ABCDEF"[(c&0xF0)>>4];
  buf[2]="0123456789ABCDEF"[c&0x0F];
  return buf;
}

void url_encode(const char* c, std::string& dist) {
  char hexbuf[]={'%',0,0,0};
  for(; *c!='\0'; c++) {
    if(is_safe_char(*c)) dist += *c;
    else if (*c==' ')    dist += '+';
    else                 dist += encode_char_to_hex(*c,hexbuf);
  }
}

#include <iostream>
int main(int argc, char** argv) {
  mmap_t mm(argv[1]);
  std::string dist;
  url_encode((const char*)mm.ptr,dist);
  // std::cout << dist;
  return 0;
}

////////////////////////////
// ファイル名: url_decode.cc
// g++ -O2 -ourl_decode url_decode.cc
// time url_decode 対象ファイル
#include <string>
#include "mmap_t.h"

static const char table[]={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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0,10,11,12,13,14,15,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,10,11,12,13,14,15,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,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,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,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
char decode_hex_to_char(const char* c) {
  // XXX: [0-9a-zA-Z]以外の全ての文字は、0として扱われる (e.g. "%@X" => 0)
  return (table[static_cast<unsigned char>(c[1])]<<4)+
          table[static_cast<unsigned char>(c[2])];
}

void url_decode(const char* c, std::string& dist) {
  for(; *c!='\0'; c++) {
    switch(*c) {
    case '%': dist += decode_hex_to_char(c); c+=2; break;  // XXX: 末尾に'%'が来る不正な文字列が渡された場合の挙動は未定義(ex. "abc%")
    case '+': dist += ' ';                         break;
    default:  dist += *c;                          break;
    }
  }  
}

#include <iostream>
int main(int argc, char** argv) {
  mmap_t mm(argv[1]);
  std::string dist;
  url_decode((const char*)mm.ptr,dist);
  //std::cout << dist;
  return 0;
}
;;;; clojure ;;;;
;;; データ準備
(def kokoro     (slurp "/path/to/kokoro"))     ; slurp関数でファイルを全部読み込めるみたい
(def enc-kokoro (slurp "/path/to/encoded-kokoro"))
;; apacheログファイルも同様に読み込む

;;; エンコード/デコード + 計時
(time (do (. java.net.Encoder encode kokoro)     'done))  ; JavaのURLエンコードクラスを使用する
(time (do (. java.net.Decoder decode enc-kokoro) 'done))  ; JavaのURLデコードクラスを使用する
/** Java **/
//// 基本的に前回と同様なので、差分だけ載せる
// ファイル: Encode.java
// 以下の四行を追加: あらかじめ慣らし運転させておくと数割程度速くなる
URLEncoder.encode(text,"UTF-8"); 
URLEncoder.encode(text,"UTF-8");
URLEncoder.encode(text,"UTF-8");        
URLEncoder.encode(text,"UTF-8");        

final long beg_t = System.currentTimeMillis();
URLEncoder.encode(text,"UTF-8");
final long end_t = System.currentTimeMillis();

// ファイル: Decode.java
// こちらも以下の四行を追加
URLDecoder.decode(text,"UTF-8");
URLDecoder.decode(text,"UTF-8");
URLDecoder.decode(text,"UTF-8");
URLDecoder.decode(text,"UTF-8");

final long beg_t = System.currentTimeMillis();
URLDecoder.decode(text,"UTF-8");
final long end_t = System.currentTimeMillis();

結果

以下結果:

【URLエンコード『こころ』のエンコード時間(秒)Apacheログのエンコード時間(秒)
url:encode(sbcl)0.0320.091
java.net.URLEncoder(Java)0.0820.265
java.net.URLEncoder(clojure)0.0260.140
url_encode(C++)0.0250.060

【URLデコード】『こころ』のデコード時間(秒)Apacheログのデコード時間(秒)
url:decode(sbcl)0.0390.246
java.net.URLDecoder(Java)0.0310.194
java.net.URLDecoder(clojure)0.0280.114
url_decode(C++)0.0070.023
気になった(?)点は二つ。

  • 同じクラス(メソッド)を使用しているのに、なぜかclojureから呼んだ方が、Javaから呼ぶより処理時間がだいぶ短い。Javaclojure共に不慣れなので理由はさっぱり分からない。逆なら納得できないこともないけど
  • 大量の文字列=バイト列を扱う処理ではやはりC++が有利(特にデコードで顕著)