2008年07月31日

社内コードコンペ - お題:最速なCIDRブロックマッチ判定〜 hamanoの場合: あ ありのまま 今 起こった事を話すぜ!『コードコンペだと思ったらゴルフコンペだった』な(ry 〜

はてなブックマークに登録

おさらい


はじめに

社内 irc で盛り上りを見せている、IPv4 アドレスの判定問題に取り組んでみました。

まず既にある実装としては、CPAN モジュールの IP::Country という実装がある様で、この実装は、判定データから2分木を構築し 1ビットずつ遷移して行くという実装のようです。

IPv4 に限定すると 1 bit ずつの判定でも高々 32回の参照になるのでこの時点ですでに O(1) なのですが、メモリをたくさん使用する代わりにもっと早い実装を考えてみると IPv4 であれば 4G のテーブルを用意しておけば、たった 1度の参照で判定出来ます。

このことから早さとメモリ空間のトレードオフとなる、この2つのアルゴリズムの間で今回のデータ量に適したところを探っていくことにしました。

実装1:このコードのウリ

まず実装が簡単そうな 1オクテットずつ遷移する実装を行ってみました。データ構造は以下のような感じで、この図では 192.168.1.2 を sakanaya の ネットワーク範囲として判定するケースを表しています。

この様なテーブルを構築しておく事で 4回の lookup で判定することが出来ます。メモリの使用量は short * 256 のテーブルが300 個程度ですので 150k でした。

実装1:コード解説

判定のコードはこんな感じになりました。

const char *cidr_lookup(unsigned char *addr, int len)
{
    return type[tab[tab[tab[tab[1][addr[0]]][addr[1]]][addr[2]]][addr[3]]];
}

そう、実はこのコードコンペ。アルゴリズムは最初から O(1) なので、アルゴリズムではなくコード(命令数)の短さが速さを決定する、ゴルフコンペだったのです。

実装1:ベンチマーク

x86

name        elapsed[sec]  average[usec]
========================================
apr        59.379924      2.375197
ip-country  3.739187      0.149567
yasui-a     2.727045      0.109082   # インラインアセンブラ
yasui-c     0.975544      0.039022   # C言語
hamano-1    0.234664      0.009386560

実装2:このコードのウリ

最初の実装で十分早かったのですが、この頃には細部までチューニングされたもっと早い実装が登場してきていたので、こちらも負けずとチューニングを行ってみることにしました。

次の実装はもう少しメモリを使用して大きなテーブルを使うことにしました。

この実装は、最初の bigtable でメモリを 32M 使用しますが、lookup が 2 回で済むの以前の実装より早くなりました。

実装2:コード解説

判定のコードはこんな感じです。ネットワークオーダーでの下位 24bit と残りの1オクテットが使いたかったので。union を定義しています。

typedef union{
    uint32_t all;
    unsigned char o[4];
}addr_t;

const char *cidr_lookup(addr_t *addr, int len)
{
    return type[tab[bigtab[addr->all & 0xffffff]][addr->o[3]]];
}

実装2:ベンチマーク

x86

name        elapsed[sec]  average[usec]
========================================
apr        59.379924      2.375197
ip-country  3.739187      0.149567
yasui-a     2.727045      0.109082   # インラインアセンブラ
yasui-c     0.975544      0.039022   # C言語
hamano-1    0.234664      0.009386
hamano-2    0.142496      0.005700

x86_64

name        elapsed[sec]  average[usec]
========================================
apr        52.340651      2.093626
ip-country  0.664034      0.026561
yasui-c     0.706095      0.028244
hamano-2    0.107557      0.004302

とうとうマシン語にして 10 ステップ程度となり、これ以上の最適化は大変そうなので、ここで断念することにしました。

最後に、この実験で使用したコードを載せておきます。探索以外の部分のコードはあまりキレイに書けていませんがご了承くださいm(--)m

実装1:コード

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DBDIR "../data"

#define MAX_TYPE 30
static char type[MAX_TYPE][PATH_MAX];
int type_index = 1;

#define MAX_TAB 1024
static unsigned short tab[MAX_TAB][256];
int tab_index = 1;

void add_tab(uint32_t addr, char type){
    int i1, i2, i3;

    if(tab_index + 3 > MAX_TAB){
        fprintf(stderr, "error: few table\n");
        return;
    }
    if(!(i1 = tab[1][addr >> 24 & 0xff])){
        tab_index++;
        i1 = tab[1][addr >> 24 & 0xff] = tab_index;
    }
    if(!(i2 = tab[i1][addr >> 16 & 0xff])){
        tab_index++;
        i2 = tab[i1][addr >> 16 & 0xff] = tab_index;
    }
    if(!(i3 = tab[i2][addr >> 8 & 0xff])){
        tab_index++;
        i3 = tab[i2][addr >> 8 & 0xff] = tab_index;
    }
    tab[i3][addr & 0xff] = type;
}

int getnetmask(int n, uint32_t *netmask)
{
  uint32_t h = 1;
  uint32_t m = 0xffffffff;
  if(n>32)
    return(0);
  n = 32 - n;
  while(n--){
    m -= h;
    h *= 2;
  }
  *netmask = m;
  return(1);
}

int initread(int fd, char file)
{
  int  r;
  int  l;
  char buff[256];
  char *p = buff;
  uint32_t ad;
  uint32_t nm = 0;
  char ip[64];
  uint32_t max;

  strcpy(type[0], "(null)");

  while((r=read(fd,p,1)) != 0){
    if(r == -1)
      return(-1);
    if(*p == '#')
      *p = 0;
    if(*p == '/')
      *p = ' ';
    if(*p == '\r')
      *p = 0;
    if(*p == '\n'){
      *p = 0;
      l = 32;
      if(sscanf(buff,"%s%d", ip, &l) < 1){
        /***** not ip addr ******/
        if(buff[0]){
          fprintf(stderr,"read error: %s(%s/%d)\n",buff,ip,l);
        }
      }else{
        if(!inet_aton(ip, (struct in_addr *)&ad)){
          fprintf(stderr,"ip addr error: %s\n", buff);
        }
        if(!getnetmask(l,&nm)){
          fprintf(stderr,"netmask error: %s\n", buff);
        }
        ad = ntohl(ad);
        ad &= nm;
        max = (1 << (32 - l)) + ad - 1;
        while(ad <= max) add_tab(ad++, file);
      }
      p = buff;
      continue;
    }
    p++;
  }
  return(0);  
}

char alloctype(char *name)
{
    char path[PATH_MAX];
    strcpy(path, name);
    strcpy(type[type_index], basename(path));
    return type_index++;
}

void cidr_initialize()
{
  int  f;
  DIR *d;
  char path[PATH_MAX];
  struct dirent *e;
  struct stat   st;
  if((d=opendir(DBDIR)) != 0){
    while((e=readdir(d)) != 0){
      sprintf(path,"%s/%s", DBDIR, e->d_name);
      if(stat(path, &st) != 0)
        continue;
      if(!S_ISREG(st.st_mode))
        continue;
      f = open(path, O_RDONLY);
      if(f == -1){
        fprintf(stderr,"file open error %s\n", path);
      }else{
        initread(f,alloctype(path));
        close(f);
      }
    }
  }
}

static inline void inet_aton2(char *p, char *o) { 
    int i=0;
    do{ 
        while (*p != '.' && *p) { 
            o[i] *= 10; 
            o[i] += *p++ - '0'; 
        }
        i++;
    }while(*p++);
}

const char *cidr_lookup(unsigned char *addr, int len)
{
    return type[tab[tab[tab[tab[1][addr[0]]][addr[1]]][addr[2]]][addr[3]]];
}

実装2:コード

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <fcntl.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define DBDIR "../data"

typedef union{
    uint32_t all;
    unsigned short half[2];
    unsigned char o[4];
}addr_t;

typedef struct{
    unsigned char type;
    uint32_t addr;
    size_t len;
}cidr_t;

#define MAX_TYPE 50
static char type[MAX_TYPE][PATH_MAX];
static char *typep[MAX_TYPE];
int type_index = 1;

#define MAX_TAB 1024
static unsigned short tab[MAX_TAB][256];
int tab_index = 1;
static unsigned short bigtab[0x1000000];
#define MAX_CIDR 1024

void add_tab(uint32_t addr, char type){
    int i1, index;

    if(tab_index + 3 > MAX_TAB){
        fprintf(stderr, "error: few table\n");
        return;
    }
    index = addr >> 24 | (addr >> 8 & 0xff00) | (addr << 8 & 0xff0000);
    if(!(i1 = bigtab[index])){
        tab_index++;
        i1 = bigtab[index] = tab_index;
    }

    tab[i1][addr & 0xff] = type;
}

int getnetmask(int n, uint32_t *netmask)
{
    uint32_t m;
    if (n < 0 || 32 < n) return 0;
    m = 1UL << (32 - n);
    --m;
    *netmask = ~m;
    return 1;
}

int initread(int fd, unsigned char t, cidr_t *cidr, unsigned int *cidr_num)
{
    int  r;
    int  l;
    char buff[256];
    char *p = buff;
    uint32_t ad;
    uint32_t nm = 0;
    char ip[64];

    while((r=read(fd,p,1)) != 0){
        if(r == -1)
            return(-1);
        if(*p == '#')
            *p = 0;
        if(*p == '/')
            *p = ' ';
        if(*p == '\r')
            *p = 0;
        if(*p == '\n'){
            *p = 0;
            l = 32;
            if(sscanf(buff,"%s%d", ip, &l) < 1){
                /***** not ip addr ******/
                if(buff[0]){
                    fprintf(stderr,"read error: %s(%s/%d)\n",buff,ip,l);
                }
            }else{
                if(!inet_aton(ip, (struct in_addr *)&ad)){
                    fprintf(stderr,"ip addr error: %s\n", buff);
                }
                if(!getnetmask(l,&nm)){
                    fprintf(stderr,"netmask error: %s\n", buff);
                }
                ad = ntohl(ad);
                ad &= nm;

                cidr[*cidr_num].addr = ad;
                cidr[*cidr_num].len = l;
                cidr[*cidr_num].type = t;
                (*cidr_num)++;
            }
            p = buff;
            continue;
        }
        p++;
    }
    return(0);  
}

unsigned char alloctype(char *name)
{
    char path[PATH_MAX];
    strcpy(path, name);
    strcpy(type[type_index], basename(path));
    typep[type_index] = type[type_index];
    return type_index++;
}

/* qsort compare function */
static int cmplen(const void *p1, const void *p2)
{
    const cidr_t *c1 = p1;
    const cidr_t *c2 = p2;
    return c1->len > c2->len;
}

void cidr_initialize()
{
    int  f;
    DIR *d;
    char path[PATH_MAX];
    struct dirent *e;
    struct stat   st;
    int i;
    uint32_t addr, max;
    cidr_t cidr[MAX_CIDR];
    unsigned int cidr_num = 0;

    typep[0] = NULL;

    if((d=opendir(DBDIR)) != 0){
        while((e=readdir(d)) != 0){
            sprintf(path,"%s/%s", DBDIR, e->d_name);
            if(stat(path, &st) != 0)
                continue;
            if(!S_ISREG(st.st_mode))
                continue;
            f = open(path, O_RDONLY);
            if(f == -1){
                fprintf(stderr,"file open error %s\n", path);
            }else{
                initread(f, alloctype(path), cidr, &cidr_num);
                close(f);
            }
        }
    }
    qsort(cidr, cidr_num, sizeof(cidr_t), cmplen);
    for(i=0; i<cidr_num; i++){
        addr = cidr[i].addr;
        max = (1 << (32 - cidr[i].len)) + addr - 1;
        while(addr <= max) add_tab(addr++, cidr[i].type);
    }
}

const char *cidr_lookup(addr_t *addr, int len)
{
    return typep[tab[bigtab[addr->all & 0xffffff]][addr->o[3]]];
}

klab_gijutsu2 at 08:00│Comments(0)TrackBack(0)開発 | codecompe

トラックバックURL

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント