Microsoft Visual C++ Compiler for Python 2.7はひとのためならず

「なさけはひとのためならず」とは、情けをかけると巡りめぐって自分に戻ってくるよ、情けをかけなさい、ポリリズムポリリズム…ループ…

Microsoft Visual C++ Compiler for Python 2.7を試す

前置き

しばらく Python 界隈のトレンドをウォッチしていなかったので、Python公式サイトの「Windowsで64bitコンパイラを使う#Python2.7」でMicrosoft Visual C++ Compiler for Python 2.7が紹介されていることを知らなかった。ページの最終更新は 2014-10-13 (月) 23:00 とあるので、このとき? とすれば結構最近だ。一部で皆お祭り騒ぎだったろうか? ワタシなら心斎橋からダイブしたいところだ。

「Microsoft Visual C++ Compiler for Python 2.7」と、あえて「for Python 2.7」としてパッケージングされなければならないのは、CPython としての Windows 版公式バイナリと、標準添付モジュール以外の C 拡張モジュールのランタイムが完全に一致していなければならないからである。

そしてまた、Microsoft 自身の手で「Microsoft Visual C++ Compiler for Python 2.7」を提供するということは、それだけ要望が多かったのであろうということとともに、Microsoft も Python が好きなのだろうということだ。スクリプト言語と .NET フレームワークとの統合「Iron*」も、もとは IronPython から始まった、はず。

Python 2.7 の場合、依存するのは、Microsoft としては「時期外れだからもういい加減乗り換えてくれよ」と言いたくもなるであろう「Visual Studio 2008 Express」なのであるから、「Microsoft Visual C++ Compiler for Python 2.7」を提供するのはかなり勇気がいることだと思う。保守しなくても良いものを自ら望んで「保守します」と宣言するようなものだから。

開発環境が違えば依存ライブラリも変わる、これは UNIX であろうと Mac だろうと同じだが、Windows の場合特に Windows Vista 以降からバージョンチェックの仕組みがより厳格になり、より開発環境の維持管理が大変「だった」。Windows での 公式 CPython ユーザが「Windows 用の専用インストーラの提供されていない OSS」に手を出しにくかったのも、開発環境の問題があったからであろう。

「Python 2.7」ではなく「Python 3.xでは?」。いや、その場合はあえて「for Python 3.x」はいらない、のではある。今は。「古いものと古いもの」の組み合わせが未だに大活躍している、がための「Microsoft Visual C++ Compiler for Python 2.7」である。Unicode の扱いの変更が手強すぎるがゆえに、大きな OSS ほどなかなか安定した Python 3.x 版を提供出来ていないでいる。そんな状態は、かつての Perl 4 から 5 への乗り換えで起こったように、少なくともあと5年はこの状態は続くであろう。「Microsoft Visual C++ Compiler for Python 2.7」も5年は維持してもらわないと困る。

Microsoft Visual C++ Compiler for Python 2.7とsetuptools最新だけでいい

Windowsで64bitコンパイラを使う」というドキュメントの元の目的は、「デフォルトで Express だけ入れても 32bit 版しか作れない」ことに回答を与えるために書かれたものであるし、追記の形での Microsoft Visual C++ Compiler for Python 2.7 の紹介となったために、結果今ややわかりにくいが、こう考えればいい:

  • CPython 2.7 公式 Windows 版とともに歩む C 拡張モジュールのためには、Microsoft Visual C++ Compiler for Python 2.7 と setuptools 6.0 以上の2つがあればいい
  • Microsoft Visual C++ Compiler for Python 2.7 があれば、32bit 版も 64bit 版もどちらも作れる

Microsoft Visual C++ Compiler for Python 2.7だけでも幸せ

実はこれ、

  • IDE (つまり「びじゅあるなかいはつかんきょう」)がいらなくて、
  • OpenMP が使いたくて
  • 32bitビルドも64bitビルドも両方必要で
  • VS 2010 ランタイム依存を避けたい Windows C++ プログラマ

も飛びつきたい内容のものである。Visual Studio 「Express」だけで済むことは、よほど「単なる勉強のため」でない限りはそう多くはなくて、「Windowsで64bitコンパイラを使う」に説明されてきた通りそれだけでは 64bit 版を作れない、であり、OpenMP を使えない、など、高等なことをしようとするとやっぱり制約があり、いつもより多めに手間隙かける必要があったのであるが、Microsoft Visual C++ Compiler for Python 2.7 は「まさにそれだけあればいい」。IDE がいらないなら、ね。(無論 C99 も C++0x、C++11 も不要なら、も込み。)

実際に入手して試してみよう。以下より入手:
Microsoft Visual C++ Compiler for Python 2.7

OpenMP を試してみるために、「/openmp コンパイラオプションの説明」より以下サンプルコードを使ってみる:

 1 // cpp_compiler_options_openmp.cpp
 2 #include <omp.h>
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 #include <windows.h>
 6  
 7 volatile DWORD dwStart;
 8 volatile int global = 0;
 9  
10 double test2(int num_steps) {
11    int i;
12    global++;
13    double x, pi, sum = 0.0, step;
14  
15    step = 1.0 / (double) num_steps;
16  
17    #pragma omp parallel for reduction(+:sum) private(x)
18    for (i = 1; i <= num_steps; i++) {
19       x = (i - 0.5) * step;
20       sum = sum + 4.0 / (1.0 + x*x);
21    }
22  
23    pi = step * sum;
24    return pi;
25 }
26  
27 int main(int argc, char* argv[]) {
28    double   d;
29    int n = 1000000;
30  
31    if (argc > 1)
32       n = atoi(argv[1]);
33  
34    dwStart = GetTickCount();
35    d = test2(n);
36    printf_s("For %d steps, pi = %.15f, %d milliseconds\n", n, d, GetTickCount() - dwStart);
37  
38    dwStart = GetTickCount();
39    d = test2(n);
40    printf_s("For %d steps, pi = %.15f, %d milliseconds\n", n, d, GetTickCount() - dwStart);
41 }

おぉ、Windows プログラム的だ。DWORD…、GetTickCount。

ともかくこれを「OpenMPプログラムとして」「32bitも64bitも」ビルドしてみるのがひとまずのゴールである。

Microsoft Visual C++ Compiler for Python 2.7のインストール場所はちょっと独特で、私の環境の場合は

C:\Users\hhsprings\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0

つまりユーザ個人の環境にインストールされ、また、インストーラはこれを選択させない。これはイヤらしいことにも思えるけれど、ただ、試してはいないが、どのみち環境変数だけの問題なので、好きに移動出来るんじゃないだろうか。

「使える」状態にするには環境変数が必要だが、

で OK。(最初気付かず自分でショートカットを作ってしまった。)

ではやってみよう。まず x86 版。

先のサンプルコードが a.cpp であるとして、

OK。(ちなみに画像で「head」を使っているが、これは Windows のツールでも Visual C++ for Python でもなく、MSYSのもの。)

次に x64 (amd64) 版。

OK、素晴らしい。

並行コンピューティング技法 ―実践マルチコア/マルチスレッドプログラミング

新品価格
¥3,456から
(2015/2/15 03:00時点)

いい加減 Python の話をしろ

はい。

まず、setuptools を、一度はインストールしたことを前提とする。6.0 以上が必要なので、最新にしなければならない。多分これが早い:

1 me@host: ~$ easy_install pip
2 me@host: ~$ pip install -U setuptools

(pipを入れてあるなら最初のは不要。)

lru-dictを例にしてみよう。lru-dict は見ての通り C で書かれていて、なおかつ Windows 用のインストーラが提供されていない。今のお題にぴったりだ。

この状態で、Visual C++ for Python 2.7 もなくインストールしようとしても以下のようになる:
error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). Get it from http://aka.ms/vcpython27
「以下のようになる」とガッカリ感を煽る言い方をしたけれど、わかっている人には「おぉ、素晴らしい」とこのエラーメッセージを見て思うのだ:

1 error: Microsoft Visual C++ 9.0 is required (Unable to find vcvarsall.bat). Get it from http://aka.ms/vcpython27

を、「Visual C++ for Python 2.7をみてる」。

でわ、Visual C++ for Python 2.7 でビルドしてみよう。

先の「ショートカット」で開発環境の環境変数がセットされているとする。

まず、素直に:

 1 me@host: lru-dict$ python setup.py build
 2 running build
 3 running build_ext
 4 building 'lru' extension
 5 creating build
 6 creating build\temp.win-amd64-2.7
 7 creating build\temp.win-amd64-2.7\Release
 8 C:\Users\hhsprings\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe /c /nologo /Ox /MD /W3 /GS- /DNDEBUG -Ic:\Python27\include -Ic:\Python27\PC /Tclru.c /Fobuild\temp.win-amd64-2.7\Release\lru.obj
 9 lru.c
10 lru.c(168) : error C2275: 'Node' : illegal use of this type as an expression
11         lru.c(58) : see declaration of 'Node'
12 lru.c(168) : error C2065: 'n' : undeclared identifier
13 lru.c(169) : error C2065: 'n' : undeclared identifier
14 lru.c(169) : warning C4047: 'function' : 'Node *' differs in levels of indirection from 'int'
15 lru.c(169) : warning C4024: 'lru_remove_node' : different types for formal and actual parameter 2
16 lru.c(170) : error C2065: 'n' : undeclared identifier
17 lru.c(170) : error C2223: left of '->key' must point to struct/union
18 lru.c(170) : error C2198: 'function through pointer' : too few arguments for call
19 lru.c(295) : error C2275: 'Node' : illegal use of this type as an expression
20         lru.c(58) : see declaration of 'Node'
21 lru.c(295) : error C2065: 'curr' : undeclared identifier
22 lru.c(296) : error C2143: syntax error : missing ';' before 'type'
23 lru.c(298) : error C2065: 'curr' : undeclared identifier
24 lru.c(299) : error C2065: 'i' : undeclared identifier
25 lru.c(299) : error C2065: 'curr' : undeclared identifier
26 lru.c(299) : warning C4047: 'function' : 'Node *' differs in levels of indirection from 'int'
27 lru.c(299) : warning C4024: 'getterfunc' : different types for formal and actual parameter 1
28 lru.c(300) : error C2065: 'curr' : undeclared identifier
29 lru.c(300) : error C2065: 'curr' : undeclared identifier
30 lru.c(300) : error C2223: left of '->next' must point to struct/union
31 error: command 'C:\\Users\\hhsprings\\AppData\\Local\\Programs\\Common\\Microsoft\\Visual C++ for Python\\9.0\\VC\\Bin\\amd64\\cl.exe' failed with exit status 2

楽しくなってきた。まぁクロスプラットフォーム、なんてこんなものである。

10分ほどソースコードを見つつ悩んでしまったが、これはなんてことはない。

 1 static void
 2 lru_delete_last(LRU *self)
 3 {
 4     if (!self->last)
 5         return;
 6 
 7     Node* n = self->last;
 8     lru_remove_node(self, n);
 9     PUT_NODE(self->dict, n->key, NULL);
10 }

この書き方、「本来の C 言語では NG」である。ブロックの途中ですき放題宣言定義が出来るようになったのは、厳密には C++ からで、C は本当はこう書かないとダメ:

 1 static void
 2 lru_delete_last(LRU *self)
 3 {
 4     Node* n; /* 最初にないとダメ。または {} でブロックを作るようにすればヨシ */
 5     if (!self->last)
 6         return;
 7 
 8     n = self->last;
 9     lru_remove_node(self, n);
10     PUT_NODE(self->dict, n->key, NULL);
11 }

ただ、結構なコンパイラはこれを許容してしまうので、開発していて気付かないことも多い。この開発者もそれをしてしまっているのであるな。

ではどうすればいいか。

チマチマとソースコードを変えてもいいけれど、「C++だとしてしまえばいい」が簡単と思う。自分個人のためなら、ね。世間にお披露目したいならもう少し慎重に考えた方がいいかもしれないが。

ともかく lru.c を lru.cpp とリネームしてしまい、setup.py を書き換えよう:

setup.py
1 module1 = Extension('lru',
2                     sources = ['lru.cpp'])

で、再度:

 1 me@host: lru-dict$ python setup.py build
 2 running build
 3 running build_ext
 4 building 'lru' extension
 5 creating build
 6 creating build\temp.win-amd64-2.7
 7 creating build\temp.win-amd64-2.7\Release
 8 C:\Users\hhsprings\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\cl.exe /c /nologo /Ox /MD /W3 /GS- /DNDEBUG -Ic:\Python27\include -Ic:\Python27\PC /Tplru.cpp /Fobuild\temp.win-amd64-2.7\Release\lru.obj
 9 lru.cpp
10 creating build\lib.win-amd64-2.7
11 C:\Users\hhsprings\AppData\Local\Programs\Common\Microsoft\Visual C++ for Python\9.0\VC\Bin\amd64\link.exe /DLL /nologo /INCREMENTAL:NO /LIBPATH:c:\Python27\libs /LIBPATH:c:\Python27\PCbuild\amd64 /EXPORT:initlru build\temp.win-amd64-2.7\Release\lru.obj /OUT:build\lib.win-amd64-2.7\lru.pyd /IMPLIB:build\temp.win-amd64-2.7\Release\lru.lib /MANIFESTFILE:build\temp.win-amd64-2.7\Release\lru.pyd.manifest
12 lru.obj : warning LNK4197: export 'initlru' specified multiple times; using first specification
13    Creating library build\temp.win-amd64-2.7\Release\lru.lib and object build\temp.win-amd64-2.7\Release\lru.exp
14 me@host: lru-dict$ 

OK。

あとはpython setup.py install でもなんでもお好きにどうぞ。

Python 2.7界隈だけでなく喜べ

VC 9.0 (VS 2008)、ということに抵抗ないなら、飛びついて欲しいぞ。私は別に「IDE嫌い」ではないけれど「IDEは使わない」人ではあって、それは単に Emacs から離れないから。そんなこともあるし、Python 関係の開発だと distutils や SCons が優秀過ぎるので「あの鬱陶しい Makefile」と付き合う機会も少ないので、ますます特殊な「ビジュアル環境」が不要になってくる。もしも、「Python とは無縁だが Windows C++ プログラマ」という人がいたら、かつ、「ビジュアル環境いらない」のなら、「for Python 2.7」にこだわらず、使えばいいと思う。