Chiharu の日記

絵描き C/C++ プログラマーの日記です。

Cygwin の stat() が遅い

Cygwin の stat() が遅いなぁと思って、高速版を実装してみました。
コードは下記の通りです。st_dev や st_ino に有効値を設定しなかったり、パス変換が厳密でないなどの制限により用途は限定されますが、ディレクトリーとファイルの判別や、タイムスタンプを確認するくらいならこの程度の実装でも足りると思います。Core i7 + SSD の実測で stat() と比べて 3〜7 倍くらいの速度が出ます。

#include <sys/stat.h>
#include <windows.h>
#include <errno.h> /* Added. */

#define T_OFFSET (((LONGLONG) 27111902 << 32) + (LONGLONG) 3577643008)

static void w32time_to_cygtime(const FILETIME *ft, struct timespec *ts);
static void cygpath_to_w32path(const char* src, char* dst);

int _local_stat(char* n, struct stat* s)
{
 WIN32_FILE_ATTRIBUTE_DATA w = { 0 };
 mode_t m;
 char path[MAX_PATH];
 
 cygpath_to_w32path(n, path);
 
 if (GetFileAttributesExA(path, GetFileExInfoStandard, &w) == FALSE) {
  errno = ENOENT; /* Added. */
  return -1;
 }
 
 if ((w.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) ||
  (w.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
  return stat(n, s); /* for symbolic link */
 }
 
 if (w.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) m = S_IFDIR;
 else m = S_IFREG;
 
 memset(s, 0, sizeof(struct stat));
 
 s->st_mode = m;
 s->st_size = w.nFileSizeLow;
 w32time_to_cygtime(&w.ftLastAccessTime, &s->st_atim);
 w32time_to_cygtime(&w.ftLastWriteTime, &s->st_mtim);
 w32time_to_cygtime(&w.ftCreationTime, &s->st_ctim);
 
 return 0;
}

void w32time_to_cygtime(const FILETIME *ft, struct timespec *ts)
{
 ts->tv_sec = (int) ((*(LONGLONG*) ft - T_OFFSET) / 10000000);
 ts->tv_nsec = (int) ((*(LONGLONG*) ft - T_OFFSET - ((LONGLONG) ts->tv_sec * (LONGLONG) 10000000)) * 100);
}

static char cygwin_path[MAX_PATH];
static unsigned cygwin_path_length = 0;

void cygpath_to_w32path(const char* src, char* dst)
{
 const char* s;
 char* d;
 char c;
 
 s = src;
 d = dst;
 
 if (*s == '/') {
  if (strncmp(s, "/cygdrive/", 10) == 0) {
   s += 10;
   
   if (*s != '\0') {
    *d++ = *s++;
    *d++ = ':';
   }
  } else {
   if (cygwin_path_length == 0) {
    HKEY key;
    DWORD size, type;
    
    if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Cygwin\\setup", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) {
        size = sizeof(cygwin_path);
     
     if (RegQueryValueExA(key, "rootdir", 0, &type, cygwin_path, &size) == ERROR_SUCCESS) {
      cygwin_path_length = size - 1;
     }
     RegCloseKey(key);
    }
    
    if (cygwin_path_length == 0) {
     strcpy(cygwin_path, "c:\\cygwin");
     cygwin_path_length = 9;
    }
   }
   
   strcpy(d, cygwin_path);
   d += cygwin_path_length;
   
   if (strncmp(s, "/usr/lib/", 9) == 0) {
    s += 4; /* /usr/lib to /lib */
   }
  }
 }
 
 while ((c = *s++) != '\0') {
  if ((c == '/') || (c == '\\')) {
   *d++ = '\\';
  } else {
   *d++ = c;
  }
 }
 
 *d = '\0';
}

なお、パスの変換に cygwin_conv_path() を使わなかったのは、当該関数が遅かったためです。実装を見ていないので詳細は分かりませんが、実測で stat() を呼び出すより遅かったです。

追記

errno の設定を忘れていたので追記しました。