Hatena::ブログ(Diary)

お前の血は何色だ!! 4 このページをアンテナに追加 RSSフィード Twitter

2011-12-25

hiphop php でPHPからジェネレートされたC++コードを読んでみよう。

PHP advent calendar です。

クリスマス過ぎましたけど 12/26日をお送りします。(google docsに名前書き忘れていたんだよ)

前回 12/25日は、@yoya さんの「Windows で PHP を build する」でした。



今回は、facebookの人が作った PHP を C++ に変換して高速動作させるという hiphop php がジェネレートする C++ のコードを読んでみたいと思います。

C++は厳格な静的型づけの言語ですが、PHPは動的型づけの言語です。

これをどうやって、変換しているのか?という話です。


非常に長く、眠い話になりますが、寝ないで読んでいただけたら嬉しいです。

間違いなどありました、ぜひ教えてください。



hiphop php を入れよう。

そもそもhiphop php を入れるのはすごく大変です。

Scientific Linux 6.1 (64bit)でインストールしたときは以下のようにしたら入りました。

#追加リポジトリ
rpm -Uvh http://pkgs.repoforge.org/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm
rpm -Uvh http://ftp.iij.ad.jp/pub/linux/fedora/epel/6/x86_64/epel-release-6-5.noarch.rpm

#アップデートと開発ツールのインストール
yum -y update
yum -y groupinstall "Development Tools"

#依存パッケージで yum で入るものを入れる.
yum -y --enablerepo=rpmforge,epel install git cmake libssh2 gd gd-devel mysql-server mysql-devel php php-cli php-common php-mbstring php-pear python-devel icu libicu-devel oniguruma oniguruma-devel flex memcached libmemcached bison re2c mysql mysql-devel libxml2 libxml2-devel libmcrypt libmcrypt-devel php-mcrypt openssl binutils binutils-devel libcap libcap-devel gd zlib bzip2 bzip2-libs bzip2-devel pcre pcre-devel expat expat-devel gd gd-devel openldap openldap-devel readline readline-devel libc-client libc-client-devel ncurses ncurses-devel pam pam-devel libmcrypt libmcrypt-devel wget tbb tbb-devel

# libmemcache
# OS標準が古いのでアップデートする
#
cd /usr/local/src/
wget http://download.tangent.org/libmemcached-0.43.tar.gz
tar -xvf libmemcached-0.43.tar.gz
cd libmemcached-0.43
./configure --prefix=/usr
make
make install


# boost
# OS標準が古いので新しいやつを /usr/に上書きで。。。
#
cd /usr/local/src/
wget 'http://sourceforge.net/projects/boost/files/boost/1.48.0/boost_1_48_0.tar.gz/download'
tar -xvf boost_1_48_0.tar.gz
cd boost_1_48_0
./bootstrap.sh
./b2 install --prefix=/usr


# download hiphop
# 先にダウンロードしないと patchが手に入らない
#
cd /usr/local/src/
git clone git://github.com/facebook/hiphop-php.git


# install libcurl  
# うちでは /usr/に上書きした
#
cd /usr/local/src/
wget http://curl.haxx.se/download/curl-7.20.0.tar.bz2
tar -xvf curl-7.20.0.tar.bz2
cp hiphop-php/src/third_party/libcurl.fb-changes.diff curl-7.20.0/
cd curl-7.20.0
sed -i 's/curl-old\///g' libcurl.fb-changes.diff
sed -i 's/curl-new\///g' libcurl.fb-changes.diff
patch -p0 < libcurl.fb-changes.diff
./configure --with-ssl --with-zlib --with-libidn --enable-sspi --enable-ldap --enable-ldaps --prefix=/usr
make
make install


# install libevent
# うちでは /usr/に上書きした
#
cd /usr/local/src/
wget http://monkey.org/~provos/libevent-1.4.13-stable.tar.gz
tar -xvf libevent-1.4.13-stable.tar.gz
cp hiphop-php/src/third_party/libevent-1.4.13.fb-changes.diff libevent-1.4.13-stable/
cd libevent-1.4.13-stable
sed -i 's/libevent-1.4.13-stable\///g' libevent-1.4.13.fb-changes.diff
sed -i 's/libevent-1.4.13-stable-fb\///g' libevent-1.4.13.fb-changes.diff
patch -p0 < libevent-1.4.13.fb-changes.diff
./configure --prefix=/usr
make
make install
ldconfig


# install hiphop
cd /usr/local/src/
cd hiphop-php/
export HPHP_HOME="/usr/local/src/hiphop-php"
export HPHP_LIB="/usr/local/src/hiphop-php/bin"
cmake -DCMAKE_PREFIX_PATH=/usr .
make
make install



#export で環境変数を定義している。ログアウトすると消えるので注意。
#毎回有効にしたい人は、  ~/.bashrc にでも。
#コピペ用
export HPHP_HOME="/usr/local/src/hiphop-php"
export HPHP_LIB="/usr/local/src/hiphop-php/bin"

参考:http://d.hatena.ne.jp/eth0jp/20101224/1293138303


libevent と curlパッチを当てないといけないのでインストールするのはなかなか大変です。

既存のRPMなどがあったりすると競合して涙目になってしまいます。

(ここらへんもhiphop php が普及しない理由の一つな気がする)


hiphop phpってどれくらい早くなるの?

苦労してインストールしたわけですから、どれくらい早くなるか気になるところです。

DBとかIOあるとそれに引きづられそうですし・・・

純粋な処理速度を見ていきたいですね。


せっかくなので、自作の Regexp_Assemble for PHP を使って速度を見て行きましょう。

Regexp_Assebmle は、正規表現を作ってくれるライブラリです。

perl の Regexp::AssebmlePHPに移植したものになります。


<?php
require_once("Regexp_Assemble.php");
$reg = new Regexp_Assemble();
$reg->add('お兄ちゃん');
$reg->add('お兄ちゃま');
$reg->add('あにぃ');
$reg->add('お兄様');
$reg->add('おにいたま');
$reg->add('兄上様');
$reg->add('にいさま');
$reg->add('アニキ');
$reg->add('兄くん');
$reg->add('兄君さま');
$reg->add('兄チャマ');
$reg->add('兄や');

//(?:お(?:兄(?:ちゃ[まん]|様)|にいたま)|兄(?:チャマ|君さま|くん|上様|や)|にいさま|あにぃ|アニキ)
echo $reg->as_string(); 

このように、単語を追加するとそれらに全てマッチする正規表現を自動的に作ってくれます。



正規表現を生成する処理は、多次元配列で管理されており、複雑な処理をCPUとメモリをフル回転させて処理します。

逆にdiskやネットワークへのIOなどはほぼありません。

純粋に処理系の速度を図りたい、このテストには向いているベンチマークだと思います。


以下のようなベンチマークを用意しました。

<?php
require_once("Regexp_Assemble.php");
for($i = 0 ; $i < 10000 ; ++$i ) {
    $reg = new Regexp_Assemble();
    $reg->add('神岸あかり');
    $reg->add('赤座あかり');
    $reg->add('黒座あかり');
    $str = $reg->as_string();

    $reg = new Regexp_Assemble();
    $reg->add('スティーブ・ジョブズ');
    $reg->add('スティーブ・ウォズアニック');
    $str = $reg->as_string();

    $reg = new Regexp_Assemble();
    $reg->add('お兄ちゃま');
    $reg->add('あにぃ');
    $reg->add('お兄様');
    $reg->add('おにいたま');
    $reg->add('兄上様');
    $reg->add('にいさま');
    $reg->add('アニキ');
    $reg->add('兄くん');
    $reg->add('兄君さま');
    $reg->add('兄チャマ');
    $reg->add('兄や');
    $str = $reg->as_string();
}

さて、ベンチマーク結果を見てみましょう。

time php bench_php.php などして計測した結果の real の値を見てみましょう。

単位は秒で、小さいほど早いです。


処理系速度
PHP 5.3.315.260s
PHP 5.4RC411.556s

PHP 5.4 で、PHPの速度も上がりました。

Regexp_Assemble for PHP は、その恩恵を受けて、PHP 5.4RC4 は PHP5.3.3の 1.32倍速となりました。

hiphop php を使うとどこまで早くなるのでしょうか?


処理系速度
PHP 5.3.315.260s
PHP 5.4RC411.556s
hiphop php5.022s

hiphop phpバイナリ変換したプログラムを、直接実行して測定しました。

src/hphp/hphp bench_php.php --keep-tempdir=1 --log=3
time /tmp/hphp_MdJIJ2/program --file bench_php.php

hiphop php早いですね。

PHP5.3の3倍、PHP5.4の 2倍以上の速さです。

こいつ赤くないのに3倍も早いゾ。

hiphop php によって facebookのwebサーバCPUトラフィックを約50%パーセント削減したというのに偽りはないようです。


http://developers.facebook.com/news.php?blog=1&story=358 の翻訳 http://blog.candycane.jp/archives/275

HipHop for PHP。
HipHopにより私たちはページによっては、Webサーバー上で約50パーセントのCPU使用量を削減できました。
CPUの使用量の少なさは、サーバー台数の削減につながり、それはより少ないオーバーヘッドを意味します。

しかし、どうやったら、こんなに高速に動作するのでしょうか?

何か魔法でも、、いやいや、プログラムは魔法で動いているのではなく、論理建てて書かれたソースコードの通りに動いています。

どんなソースコードだとこんなにも早くなるのでしょう?

今回は hiphop phpがどうやって、 PHPC++ に変換しているのかについてみていきたいと思います。

(ここまで前ふり)


ながい たびが はじまる

f:id:rti7743:20111225210347p:image

ジェネレートされたコードを読もう。

hiphop phpインストールすると、 hphpコマンドで PHPC++ に変換し、コンパイルして実行してくれます。

-m server オブジョンを付けると、自分がhttpdにもなって、web応答を返してくれます。


hiphop php は /tmp/ 以下に、 ディレクトリを作ってコードを吐きます。

コンパイルが終わると変換のソースなどは消されちゃうので、削除されないように --keep-tempdir=1 オプションをつけます。

また、どのテンポラリに吐かれたかわからなくなるので、 --log=3 オプションをつけます。

src/hphp/hphp test.php --keep-tempdir=1 --log=3

変換されたソースは以下のようなディレクトリ構成になっています。

名前役割
CMakeFilesメイク関係。コンパイルした中間ファイルなど。見なくていい。
phpPHPC++に変換したコードが格納される。主戦場
syshiphop php のシステム関係でPHP単位で変わるものがはいるみたい。
clsphp classを変換した結果。クラスが無い時は作られない。

これ以外にも、 hiphop php 自体がもっているランタイムがあります。

ランタイムは、hiphopインストールしたディレクトリsrc/runtime/ 以下にあります。

今回は /usr/local/src/hiphop-php/src/runtime になります。




今回は、 phpがどのように変換されるかなので、phpディレクトリの中を見て行きましょう。

ここには、PHPのファイル名と同名の cpp があります。

test.php に対応するには、 test.cpp test.h test.fws.h です。


早速 hello wolrdをやってみましょう。

以下のような hello worldを用意しました。

<?php
echo "hello world";

では、hiphop phpC++に変換します。

src/hphp/hphp test.php --keep-tempdir=1 --log=3

logオプションにより詳細な出力がされます。

running hphp...
creating temporary directory /tmp/hphp_vlzsBB ...
parsing inputs...
parsing inputs took 0'00" (5 ms) wall time
pre-optimizing...
pre-optimizing took 0'00" (0 ms) wall time
inferring types...
inferring types took 0'00" (0 ms) wall time
post-optimizing...
post-optimizing took 0'00" (0 ms) wall time
creating CPP files...
creating CPP files took 0'00" (48 ms) wall time
compiling and linking CPP files...

compiling and linking CPP files took 0'56" (56326 ms) wall time
running executable /tmp/hphp_vlzsBB/program --file test.php...
hello worldall files saved in /tmp/hphp_vlzsBB ...
running hphp took 0'56" (56802 ms) wall time



さて、ソースコードを見てみましょう。

元のPHPのコード test.php

<?php
echo "hello world";


hphp_test/php/test.h


#ifndef __GENERATED_php_test_h70588221__
#define __GENERATED_php_test_h70588221__


// Declarations

namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

// Includes and Functions
Variant pm_php$test_php(bool incOnce, LVariableTable* variables, Globals *globals);

// Constants

///////////////////////////////////////////////////////////////////////////////
}

#endif // __GENERATED_php_test_h70588221__

php/test.fws.h


#ifndef __GENERATED_php_test_fws_h1c89c124__
#define __GENERATED_php_test_fws_h1c89c124__


namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

// 1. Static Strings
extern StaticString s_ss3994978b;

// 2. Static Arrays

// 3. Static Variants





///////////////////////////////////////////////////////////////////////////////
}


#endif // __GENERATED_php_test_fws_h1c89c124__

hphp_test/php/test.cpp

#include <runtime/base/hphp.h>
#include <sys/literal_strings_remap.h>
#include <sys/scalar_arrays_remap.h>
#include <sys/scalar_integers_remap.h>
#include <sys/global_variables.h>
#include <sys/cpputil.h>
#include <php/test.fws.h>
#include <php/test.h>

// Dependencies
#include <runtime/ext/ext.h>
namespace hphp_impl_starter {}

namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
Variant pm_php$test_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test.php, pm_php$test_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  echo(NAMSTR(s_ss3994978b, "hello world"));
  return true;
}
namespace hphp_impl_splitter {}

///////////////////////////////////////////////////////////////////////////////
}

hello world文字列が見えますね。

NAMVARってマクロがありますね。これは何でしょうか?

runtime/base/macros.h で以下のように定義されています。

#define NAMVAR(nam, str)    (nam)

むむ、つまり、文字列の方はデバッグに人間が読めるように貼っているだけというだけですか。

echo(NAMSTR(s_ss3994978b, "hello world"));
↓
↓NAMSTR マクロで置換
↓
echo(s_ss3994978b);

マクロ置換されて残った s_ss3994978bって何んでしょう?

これは、php/test.fws.h ヘッダファイルに定義があります。

hphp_test/php/test.fws.h

extern StaticString s_ss3994978b;

extern されていますね。実態はどこでしょう?

sys\literal_strings_0.no.cpp

#include <runtime/base/complex_types.h>
#include <sys/literal_strings_remap.h>


namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

StaticString s_ss3994978b("hello world");

void init_literal_varstrings() {
  extern void sys_init_literal_varstrings();
  sys_init_literal_varstrings();
}

///////////////////////////////////////////////////////////////////////////////
}

定義がありました。

StaticString s_ss3994978b("hello world"); 

s_ss3994978bは、StaticString型ですね。

StaticString型とは、どういう定義がされているのでしょうか?


runtime/base/type_string.h

/**
 * A StaticString can be co-accessed by multiple threads, therefore they are
 * not thread local, and they have to be allocated BEFORE any thread starts,
 * so that they won't be garbage collected by MemoryManager. This is used by
 * constant strings, so they can be pre-allocated before request handling.
 */
class StaticString : public String {
public:
  static StringDataSet &TheStaticStringSet();
  static void FinishInit();
  static void ResetAll(); // only supposed to be called during program shutdown

public:
  friend class StringUtil;
  friend class LiteralStringInitializer;

  StaticString(litstr s);
  StaticString(litstr s, int length); // binary string
  StaticString(std::string s);
  StaticString(const StaticString &str);
  ~StaticString() {
    // prevent ~SmartPtr from calling decRefCount after data is released
    m_px = NULL;
  }
  StaticString& operator=(const StaticString &str);

private:
  void init(litstr s, int length);
  void insert();

  StringData m_data;
  static StringDataSet *s_stringSet;
};

extern const StaticString empty_string;

面白いのは、 std::string ではないということです。

すべて自作しないと嫌な人なのかな。

class StaticString : public String 
↓
class String : public SmartPtr<StringData> 
↓
class StringData

これでよくわかりました。

つまり、hello world のコードをもっと単純化して書くと、こんな感じになります。

echo(NAMSTR(s_ss3994978b, "hello world"));
↓
↓
↓
StaticString s_ss3994978b("hello world");
echo(s_ss3994978b);

hello world! が、わかった所で、次からPHPの主要な機能がどういうC++コードになっていくかを見ています。

これから先は、結構長くなるので、ヘッダーincludeやネームスペースなどの省略しています。

ちゃんとしたものが見たい方は、zipダウンロードして下さい。

zipには、今回の記事を書くため調査したデータ一式が入っています。

http://rtilabs.net/files/2011_12_25/hphp_test.zip

変数は?

次に変数を使ってみましょう。

test8.php

<?php
$a = 1;

$b = "hello";

$c = 2;
$c = "str";

$d = array();
$d[] = 10;
$d['ex'] = 20;
$d[] = 30;

var_dump($a,$b,$c,$d);

hphp_test8/php/test8.fws.h

// 1. Static Strings
extern StaticString s_ss27b9150a;
extern StaticString s_ssf8576bd5;
extern StaticString s_ss5c5509b0;
extern StaticString s_ssaa9bf7c4;
extern StaticString s_ssd2bea2d3;
extern StaticString s_sse6f62137;
extern StaticString s_ss8f83bd9c;

// 2. Static Arrays
extern StaticArray s_sa00000000;

// 3. Static Variants
extern const VarNR &s_svi542bad8b;
extern const VarNR &s_svid7a79683;
extern const VarNR &s_svifc87a5f7;

hphp_test8/php/test8.cpp

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
Variant pm_php$test8_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test8.php, pm_php$test8_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);
  Variant &v_c ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss5c5509b0, "c")) : g->GV(c);
  Variant &v_d ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssaa9bf7c4, "d")) : g->GV(d);

  v_a = 1LL;
  v_b = NAMSTR(s_sse6f62137, "hello");
  v_c = 2LL;
  v_c = NAMSTR(s_ss8f83bd9c, "str");
  v_d = s_sa00000000;
  v_d.append((NAMVAR(s_svi542bad8b, 10LL)));
  v_d.set(NAMSTR(s_ssd2bea2d3, "ex"), (NAMVAR(s_svid7a79683, 20LL)), true);
  v_d.append((NAMVAR(s_svifc87a5f7, 30LL)));
  LINE(14,(x_var_dump(4, v_a, Array(array_createvi(3, toVPOD(v_b), toVPOD(v_c), toVPOD(v_d))))));
  return true;
}

PHP変数は、Variant型で表現しています。

Variantといえば、型を持たないなんでも入れられる型の名称によく使われる名前です。

PHPの型を持たない変数を表現するのに適した名前だと思います。


さて、Variant型はどう実装されているのでしょうか?

runtime/base/type_variant.h

class Variant {
 public:
  friend class Array;
  friend class VariantVectorBase;

  /**
   * Variant does not formally derive from Countable, however it has a
   * _count field and implements all of the methods from Countable.
   */
  IMPLEMENT_COUNTABLE_METHODS_NO_STATIC
  
  
  中略

  /**
   * Constructors. We can't really use template<T> here, since that will make
   * Variant being able to take many other external types, messing up those
   * operator overloads.
   */
  Variant(bool    v) : _count(0), m_type(KindOfBoolean) { m_data.num = (v?1:0);}
  Variant(int     v) : _count(0), m_type(KindOfInt32  ) { m_data.num = v;}
  Variant(int64   v) : _count(0), m_type(KindOfInt64  ) { m_data.num = v;}
  Variant(uint64  v) : _count(0), m_type(KindOfInt64  ) { m_data.num = v;}
  Variant(long    v) : _count(0), m_type(KindOfInt64  ) { m_data.num = v;}
  Variant(double  v) : _count(0), m_type(KindOfDouble ) { m_data.dbl = v;}

  中略
};

_count と m_typeから、参照カウントをもっていて、何型か管理するフラグを使った Variant であるということがわかります。

ここでは、C++boost any ではなく自前で実装されています。。。


また、 template を使った Variant ではなく、昔ながらの何型かのフラグを保持するVariant で実装されています。

PHP の データ型との相性なのでしょうか? それとも、boost any のように template で作る場合 new が発生してしまうので、それを避けるためでしょうか?

知っている人がいたら教えて下さい。


コードを比較してみよう。

さて、コードが長くなってしまって見通しが悪いですね。

PHPC++を対訳して比較してみましょう。

PHP>$a = 1;
↓
C++>v_a = 1LL;


PHP>$b = "hello";
↓
C++>v_b = NAMSTR(s_sse6f62137, "hello");


PHP>$c = 2;
PHP>$c = "str";
↓
C++>v_c = 2LL;
C++>v_c = NAMSTR(s_ss8f83bd9c, "str");


PHP>$d = array();
PHP>$d[] = 10;
PHP>$d['ex'] = 20;
PHP>$d[] = 30;
↓
C++>v_d = s_sa00000000;
C++>v_d.append((NAMVAR(s_svi542bad8b, 10LL)));
C++>v_d.set(NAMSTR(s_ssd2bea2d3, "ex"), (NAMVAR(s_svid7a79683, 20LL)), true);
C++>v_d.append((NAMVAR(s_svifc87a5f7, 30LL)));

みごとにPHPのコードが Variant型を等して C++ で表現されています。

面白いですね。


変数がだいたい分かったところで、次は配列処理について見ていきます。

配列

前回のソースから配列処理の部分を見てみましょう。

PHP>$d = array();
PHP>$d[] = 10;
PHP>$d['ex'] = 20;
PHP>$d[] = 30;
↓
C++>v_d = s_sa00000000;
C++>v_d.append((NAMVAR(s_svi542bad8b, 10LL)));
C++>v_d.set(NAMSTR(s_ssd2bea2d3, "ex"), (NAMVAR(s_svid7a79683, 20LL)), true);
C++>v_d.append((NAMVAR(s_svifc87a5f7, 30LL)));

対訳で見て行きましょう。

$d[] = 10;

↓というコードは、appendメソッドの呼び出しに変わりました。↓

v_d.append((NAMVAR(s_svi542bad8b, 10LL)));

$d['ex'] = 20;

↓というコードは、setメソッドの呼び出しに変わりました。↓

v_d.set(NAMSTR(s_ssd2bea2d3, "ex"), (NAMVAR(s_svid7a79683, 20LL)), true);


ところで、最初の初期化に利用している s_sa00000000 とはなんなのでしょうか?

C++>v_d = s_sa00000000;
C++>v_d.append((NAMVAR(s_svi542bad8b, 10LL)));
C++>v_d.set(NAMSTR(s_ssd2bea2d3, "ex"), (NAMVAR(s_svid7a79683, 20LL)), true);
C++>v_d.append((NAMVAR(s_svifc87a5f7, 30LL)));

s_sa00000000 の定義ですが、php/test8.fws.h で定義されています。

extern StaticArray s_sa00000000;

extern なので、実態は別です。ただ、型はわかりました。

StaticArray型です。StaticArray型の定義を見てみましょう。


runtime/base/type_array.h

/**
 * A StaticArray can be co-accessed by multiple threads, therefore they are
 * not thread local, and they have to be allocated BEFORE any thread starts,
 * so that they won't be garbage collected by MemoryManager. This is used by
 * scalar arrays, so they can be pre-allocated before request handling.
 */
class StaticArray : public Array {
public:
  StaticArray() { }
  StaticArray(ArrayData *data) : Array(data) {
    m_px->setStatic();
    m_px->onSetStatic();
  }
  ~StaticArray() {
    // prevent ~SmartPtr from calling decRefCount after data is released
    m_px = NULL;
  }
};

変数は Variant型 がやりますし、 StaticArray型は何をするのでしょうか?

StaticArray で行われている機能のひとつで、静的に決定できる配列最適化があります。


自明な配列最適化

test5.php

<?php
$a = array();
$a[] = 0;
$a['er'] = 'x';
$a['acx'] = array('oge','hoge');
$a[] = 2;

var_dump($a);

これをC++に変換すると以下のようになりました。

hphp_test5/php/test5.cpp

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
Variant pm_php$test5_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test5.php, pm_php$test5_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  v_a = s_sa00000000;
  v_a.append((NAMVAR(s_svif01bca90, 0LL)));
  v_a.set(NAMSTR(s_ssfde820e1, "er"), (NAMVAR(s_svse59fa416, "x")), true);
  v_a.set(NAMSTR(s_ssf04a85df, "acx"), (s_sva905e1b1b), true);
  v_a.append((NAMVAR(s_svi90d5f98c, 2LL)));
  LINE(8,(x_var_dump(1, v_a)));
  return true;
}

対訳で載せてみましょう。

PHP>$a = array();
C++>v_a = s_sa00000000;

PHP>$a[] = 0;
C++>v_a.append((NAMVAR(s_svif01bca90, 0LL)));

PHP>$a['er'] = 'x';
C++>v_a.set(NAMSTR(s_ssfde820e1, "er"), (NAMVAR(s_svse59fa416, "x")), true);

PHP>$a['acx'] = array('oge','hoge');
C++>v_a.set(NAMSTR(s_ssf04a85df, "acx"), (s_sva905e1b1b), true); //あれ?

PHP>$a[] = 2;
C++>v_a.append((NAMVAR(s_svi90d5f98c, 2LL)));

array('oge','hoge'); の姿がありません。

代わりに s_sva905e1b1b という変数になっています。


PHP>$a['acx'] = array('oge','hoge');
C++>v_a.set(NAMSTR(s_ssf04a85df, "acx"), (s_sva905e1b1b), true); //あれ?

array('oge','hoge');の代わりに書いてある s_sva905e1b1b って何でしょうか?


sys\scalar_arrays.no.cpp

StaticArray s_sa905e1b1b;
VarNR s_sva905e1b1b;
StaticArray s_sa00000000;

void ScalarArrays::initializeNamed() {
  s_sa905e1b1b = sa_[1];
  s_sva905e1b1b = s_sa905e1b1b;
  s_sa00000000 = sa_[0];
}

回りくどいですが、 sa_[1] という変数の値になっています。

sa_[1]とは何でしょうか?

array('oge','hoge') を固定化したデータなのです。

しかもデータには gz 圧縮をされています。

sys\scalar_arrays_0.no.cpp

static const char sa_cdata[63] = {
  0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 
  0x4b, 0xb4, 0x32, 0xb2, 0xaa, 0xce, 0xb4, 0x32, 0xb0, 0x4e, 
  0xb4, 0x32, 0xb0, 0xaa, 0xae, 0xcd, 0xb4, 0x32, 0x04, 0xb2, 
  0xa0, 0x42, 0xc5, 0x56, 0xc6, 0x56, 0x4a, 0xf9, 0xe9, 0xa9, 
  0x4a, 0xd6, 0x20, 0xe1, 0x62, 0x2b, 0x13, 0x2b, 0xa5, 0x0c, 
  0x30, 0xb7, 0xb6, 0x16, 0x00, 0x63, 0x28, 0xf5, 0x8a, 0x37, 
  0x00, 0x00, 0x00, };

StaticArray ScalarArrays::sa_[2];

void ScalarArrays::initialize() {
  SystemScalarArrays::initialize();
  ArrayUtil::InitScalarArrays(sa_, 2, sa_cdata, 63);
  ScalarArrays::initializeNamed();
}


ArrayUtil::InitScalarArraysという関数初期化していますが、この関数の実体はこんな感じです。

runtime/base/array/array_util.cpp

void ArrayUtil::InitScalarArrays(Array arrs[], int nArrs,
                                 const char *scalarArrayData,
                                 int scalarArrayDataSize) {
  int len = scalarArrayDataSize;
  char *uncompressed = gzdecode(scalarArrayData, len);
  if (uncompressed == NULL) {
    throw Exception("Bad scalarArrayData %p", scalarArrayData);
  }
  String s = String(uncompressed, len, AttachString);
  Variant v(f_unserialize(s));
  ASSERT(v.isArray());
  Array scalarArrays =  v;
  ASSERT(scalarArrays.size() == nArrs);
  for (int i = 0; i < nArrs; i++) {
    arrs[i] = scalarArrays[i];
    arrs[i].setStatic();
  }
}

データをPHPの命令でもあるgzdecodeで解凍していると思われます。

hiphopPHPの命令を自前で再実装しているそうなので、それの使い回しですね。

初期化で1度だけデータを解凍するので、余計なCPUコストも掛かりませんし、圧縮すること実行ファイルが小さくなるメリットがあります。

(とはいっても、hiphopが生成するバイナリは巨大ですけど・・・)


変数文字列呼び出し

test13.php

<?php

$a = "b";
$b = 20;

$c = $$a;
var_dump($c);

PHPには、 $$a のように、変数の中に入っている文字列が表している変数名にアクセスすることができます。

これを行う C++ コードはどうなるでしょうか?


hphp_test13/php/test13.cpp

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
Variant pm_php$test13_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test13.php, pm_php$test13_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);
  Variant &v_c ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss5c5509b0, "c")) : g->GV(c);

  v_a = NAMSTR(s_ssf8576bd5, "b");
  v_b = 20LL;
  v_c.assignVal(variables->get(toString(v_a)));
  LINE(7,(x_var_dump(1, v_c)));
  return true;
}

variables->get というのがそれっぽいですね。

コードを対訳させてみましょう。


$c = $$a;
↓
v_c.assignVal(variables->get(toString(v_a)));

variablesは、 Variant pm_php$test13_php(bool incOnce, LVariableTable* variables, Globals *globals) にあるとおりです。

ローカル変数を管理するエリアだと思われます。

LVariableTable型は、 Arrayから派生しているクラスみたいです。

Arrayから派生させたのは便宜上だとは思います。

PHPの場合、「変数名」が重要になってくるため、$arr[変数名]=値 と、PHPのMapチックに使える Array構造が便利だったからでしょう。


LVariableTable型の定義を見てみましょう。

runtime/base/variable_table.h

/**
 * L-value variable table that can get/set a variable's value by its name. The
 * reason we have both RVariableTable and LVariableTable, instead of just this
 * one, is because LVariableTable requires all variables to be Variant type,
 * taking away type inference. If we can tell no dynamic variable was used in
 * l-value context, we don't have to use LVariableTable. Of course, ideally
 * even RVariableTable is not needed, meaning no dynamic variable is ever used.
 */
class LVariableTable : public Array {
 public:
  virtual ~LVariableTable() {}
  Variant &get(CVarRef s) { return getImpl(s.toString()); }
  Variant &get(CStrRef s) { return getImpl(s); }
  Variant &get(litstr  s) { return getImpl(s);}
  virtual Variant &getVar(CStrRef s, SuperGlobal sg) {
    ASSERT(sg == SgNormal);
    return getImpl(s);
  }

  /**
   * Code-generated sub-class may override this function by generating one
   * entry per variable.
   */
  virtual bool exists(CStrRef s) const {
    return Array::exists(s, true);
  }

  /**
   * Code-generated sub-class will implement this function by generating one
   * entry per variable.
   */
  virtual Variant &getImpl(CStrRef s);

  virtual Array getDefinedVars();
};

変数がどのように変換されるかだいたいわかりました。

次は、関数がどのように扱われていくのか見ていきます。

関数

PHP関数がどのように変換されるか見ていきます。

test12.php

<?php
function myfunc($v)
{
    return $v + 1;
}

$a = myfunc(3);
var_dump($a);

hphp_test12/php/test12.cpp

/* preface starts */
extern CallInfo ci_;
extern CallInfo ci_myfunc;
/* preface finishes */
/* SRC: test12.php line 3 */
Numeric f_myfunc(CVarRef v_v) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_v)), r);
  return (v_v + 1LL);
}
namespace hphp_impl_splitter {}
Variant ifa_myfunc(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("myfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (f_myfunc(arg0));
}
Variant i_myfunc(void *extra, CArrRef params) {
  return invoke_func_few_handler(extra, params, &ifa_myfunc);
}
CallInfo ci_myfunc((void*)&i_myfunc, (void*)&ifa_myfunc, 1, 0, 0x0000000000000000LL);
Variant pm_php$test12_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test12.php, pm_php$test12_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  {
    LINE(8,0);
    const Numeric &tmp0((f_myfunc(NAMVAR(s_svia6bfbbdd, 3LL))));
    v_a.assignVal(tmp0);
  }
  LINE(9,(x_var_dump(1, v_a)));
  return true;
}

ずいぶん長い C++ コードになりました。

myfunc と名前のつく関数が3つもあります。

f_myfunc , ifa_myfunc , i_myfunc 

ただ、直接呼ばれているのは、関数の実体が書いてある f_myfunc だけのようです。

{
   LINE(8,0);
   const Numeric &tmp0((f_myfunc(NAMVAR(s_svia6bfbbdd, 3LL))));
   v_a.assignVal(tmp0);
}

  ↓↓↓f_myfunc を呼び出す↓↓

Numeric f_myfunc(CVarRef v_v) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_v)), r);
  return (v_v + 1LL);
}
  

他は何に使っているのでしょうか? 今はわからないので、置いといて先に進みましょう。


関数の実体を見てみます。

Numeric f_myfunc(CVarRef v_v) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_v)), r);
  return (v_v + 1LL);
}

戻り値が Numeric型になっています。

これは、関数の中身を見て、戻り値に int型を返すパスしかないので最適化してくれた!!と思いますが、、、、実は、ただの、 Variant の typedef です。

runtime/base/types.h

typedef Variant Numeric;

int値が Variantを経由するなんてやや残念です。

int値が2^31を超えてしまったときに float型に変換されるとかの PHP の仕様が響いているのでしょうか?



他の型を返す場合はどうなるのでしょうか?

他の方を返す関数を定義してみて、挙動を観察してみます。

文字列のみを返す関数

文字列のみを返す場合は String型になります。

test28.php

<?php

function myfunc($x, $v = 1)
{
     return "abc!" . $x . $v;
}

$a = myfunc(5);
var_dump($a);


test28/php/test28.cpp

String f_myfunc(CVarRef v_x, CVarRef v_v //  = NAMVAR(s_svib794f8ce, 1LL)
) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(2, toVPOD(v_x), toVPOD(v_v)), r);
  return concat3(NAMSTR(s_ss34734aeb, "abc!"), toString(v_x), toString(v_v));
}

String型は、Variant ではなく、文字列を格納する専用の型です。

runtime/base/type_string.h

class String : public SmartPtr<StringData> {
略
}

継承している StringData型はトップレベルのクラスです。

runtime/base/string_data.h

class StringData {
略
}

Arrayを返す場合は

<?php
function myfunc($x)
{
     return array($x);
}

$a = myfunc(5);
var_dump($a);

Array f_myfunc(CVarRef v_x) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_x)), r);
  return Array(array_createvi(1, toVPOD(v_x)));
}

Array型は、Variant ではなく、配列を格納する専用の型です。

runtime/base/type_array.h

class Array : public SmartPtr<ArrayData> {
略
}

継承している ArrayData型は以下のように定義されています。

runtime/base/array/array_data.h

class ArrayData : public Countable {
略
}

関数を返す場合

<?php
function myfunc($x)
{
     return function()
     {
         return 1;
     };
}

$a = myfunc(5);
var_dump($a);
p_Closure f_myfunc(CVarRef v_x) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_x)), r);
  return LINE(7,(p_Closure(NEWOBJ(c_Closure)(&ci_07358007151305723156_1, NULL))));
}

配列を返す場合、p_Closure という型になります。

クロージャの厳密な定義とはちょっと違ってややこしいですが、そういう名前になっています。


p_Closure型ですが、これはちょっとトリッキーな名前づけをやっているようです。

p_Closure型の大元の型が c_Closure 型です。

runtime/ext/ext_closure.h

FORWARD_DECLARE_CLASS_BUILTIN(Closure);
class c_Closure : public ExtObjectData {
略
}

このとき、FORWARD_DECLARE_CLASS_BUILTINマクロによって、 typedef SmartObject<c_Closure> p_Closure; が発行されて、p_Closure型ができるみたいです。



なんつーか、grepしにくいから、こーゆー書き方やめてほしいです。。


runtime/base/macros.h

#define FORWARD_DECLARE_CLASS(cls)              \
  class c_##cls;                                \
  typedef SmartObject<c_##cls> p_##cls;         \

#define FORWARD_DECLARE_CLASS_BUILTIN(cls)      \
  FORWARD_DECLARE_CLASS(cls)                    \
  extern ObjectData *coo_##cls();               \
  extern const ObjectStaticCallbacks cw_##cls;

文字列かNULLを帰す場合

<?php
function myfunc($x)
{
    if ($x % 2 == 0)
    {
        return $x;
    }
    return NULL;
}

$x = myfunc(5);
var_dump($x);
Variant f_myfunc(CVarRef v_x) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_x)), r);
  if (equal(modulo(toInt64(v_x), 2LL), 0LL)) {
    {
      return v_x;
    }
  }
  return null;
}

ごちゃ混ぜ

<?php
function myfunc($x)
{
    if ($x % 4 == 0)
    {
        return $x;
    }
    else if ($x % 3 == 0)
    {
        return "";
    }

    return NULL;
}

$x = myfunc(5);
var_dump($x);

Variant f_myfunc(CVarRef v_x) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_x)), r);
  if (equal(modulo(toInt64(v_x), 4LL), 0LL)) {
    {
      return v_x;
    }
  }
  else if (equal(modulo(toInt64(v_x), 3LL), 0LL)) {
    {
      return NAMSTR(s_ss00000000, "");
    }
  }
  return null;
}

いろんなバリエーションを帰す場合は Variant になります。

これは当然といえば当然ですね。


戻り値を返さない関数

最後に、戻り値を返さない関数はどのように変換されるのでしょうか?

test15.php

<?php
function say($str)
{
    echo $str;
}

say("hello world!");
void f_say(CVarRef v_str) {
  FUNCTION_INJECTION_NOMEM(say);
  INTERCEPT_INJECTION("say", array_createvi(1, toVPOD(v_str)), );
  echo(toString(v_str));
}

ちゃんと戻り値が void 型になりました。

関数戻り値のまとめ

場合備考
数値型NumericVariantのtypedef
文字型String
配列Array
関数p_Closuretypedef SmartObject<c_Closure> p_Closure;
別の値と混合Variant
値を返さないvoid

将来の最適化に期待したいところです。


関数のディフォルト引数

<?php
function myfunc($x, $v = 1)
{
     return $x + $v + 1;
}

$a = myfunc(5);
var_dump($a);

PHP には、 function myfunc($x, $v = 1) のように、 ディフォルト引数がつけられますね。

これは、どうなるのでしょうか?

/* preface finishes */
/* SRC: test14.php line 3 */
Numeric f_myfunc(CVarRef v_x, CVarRef v_v //  = NAMVAR(s_svib794f8ce, 1LL)
) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(2, toVPOD(v_x), toVPOD(v_v)), r);
  return (v_x + (v_v + 1LL));
}
namespace hphp_impl_splitter {}
Variant ifa_myfunc(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("myfunc", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  CVarRef arg1(count <= 1 ? (NAMVAR(s_svib794f8ce, 1LL)) : a1);
  return (f_myfunc(arg0, arg1));
}
Variant i_myfunc(void *extra, CArrRef params) {
  return invoke_func_few_handler(extra, params, &ifa_myfunc);
}
CallInfo ci_myfunc((void*)&i_myfunc, (void*)&ifa_myfunc, 2, 0, 0x0000000000000000LL);
Variant pm_php$test14_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test14.php, pm_php$test14_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  {
    LINE(8,0);
    const Numeric &tmp0((f_myfunc(NAMVAR(s_svi6a15d700, 5LL))));
    v_a.assignVal(tmp0);
  }
  LINE(9,(x_var_dump(1, v_a)));
  return true;
}

前回同様、 myfunc という名前の関数が3つ作られています。


関数の実態を見てみましょう。

Numeric f_myfunc(CVarRef v_x, CVarRef v_v //  = NAMVAR(s_svib794f8ce, 1LL)
) {
  FUNCTION_INJECTION(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(2, toVPOD(v_x), toVPOD(v_v)), r);
  return (v_x + (v_v + 1LL));
}

第二引数 v_v があります。

親切にも、 CVarRef v_v // = NAMVAR(s_svib794f8ce, 1LL) と、コメントが振られています。

ヘッダーファイルの方をみるとなんてことはないC++ディフォルトの引数です。

php/test14.h

// Includes and Functions
Numeric f_myfunc(CVarRef v_x, CVarRef v_v = NAMVAR(s_svib794f8ce, 1LL));
Variant pm_php$test14_php(bool incOnce, LVariableTable* variables, Globals *globals);

これは、C++の特性で、ディフォルト引数をヘッダーか、実態のどちらかにしか書けないためです。

まーいけていないところですが、歴史が長い言語だけに仕方ないのかもしれません。

関数引数の型指定

型がないPHPですが、 array型などは関数引数で型指定をすることができます。

型を指定することでC++に変換するコードがどう変わるか見てみましょう。

test16.php

<?php

function myfunc(array $arr) //arrayのみ
{
   return $arr[0];
}

$a = array();
$a[] = 'a';
$a[] = 'b';
$a[] = 'c';

$b = myfunc($a);
var_dump($b);

extern CallInfo ci_myfunc;
extern CallInfo ci_;
/* preface finishes */
/* SRC: test16.php line 3 */
Variant ft_myfunc(CArrRef v_arr) {
  FUNCTION_INJECTION_NOMEM(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_arr)), r);
  return LINE(5,(v_arr.rvalAt(0LL, AccessFlags::Error)));
}
Variant f_myfunc(CVarRef v_arr) {
  if(!v_arr.isArray()) {
    throw_unexpected_argument_type(1,"myfunc()","Array",v_arr);
    return null;
  }
  return ft_myfunc(v_arr.toCArrRef());
}
namespace hphp_impl_splitter {}
namespace hphp_impl_splitter {}
Variant ifa_myfunc(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) return throw_missing_typed_argument("myfunc", 0, 1);
  CVarRef arg0(a0);
  return (f_myfunc(arg0));
}
Variant i_myfunc(void *extra, CArrRef params) {
  return invoke_func_few_handler(extra, params, &ifa_myfunc);
}
CallInfo ci_myfunc((void*)&i_myfunc, (void*)&ifa_myfunc, 1, 0, 0x0000000000000000LL);
Variant pm_php$test16_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test16.php, pm_php$test16_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);

  v_a = s_sa00000000;
  v_a.append((NAMVAR(s_svs27b9150a, "a")));
  v_a.append((NAMVAR(s_svsf8576bd5, "b")));
  v_a.append((NAMVAR(s_svs5c5509b0, "c")));
  {
    LINE(13,0);
    const Variant &tmp0((f_myfunc(v_a)));
    v_b.assignVal(tmp0);
  }
  LINE(14,(x_var_dump(1, v_b)));
  return true;
}

myfuncという名前の関数が3つから1つ増えて、4つになってしまいました。

注目したいのは、 今まで関数の実体を定義していた f_myfunc が ft_myfuncへ処理を投げるプロキシーになった点です。

//今までここに実態があったが
Variant f_myfunc(CVarRef v_arr) {
  if(!v_arr.isArray()) {
    throw_unexpected_argument_type(1,"myfunc()","Array",v_arr);
    return null;
  }
  return ft_myfunc(v_arr.toCArrRef());
}
//関数の実体が移動された
Variant ft_myfunc(CArrRef v_arr) {
  FUNCTION_INJECTION_NOMEM(myfunc);
  INTERCEPT_INJECTION("myfunc", array_createvi(1, toVPOD(v_arr)), r);
  return LINE(5,(v_arr.rvalAt(0LL, AccessFlags::Error)));
}

f_myfunc で function myfunc(array $arr) の型チェックが入ってしまいました。

型を指定してあげることで、余計な処理が増えてしまいました。。。

型を明確に書くと、より最適化されたソースがジェネレートされるかと思いきや、余計な処理が入って遅くなってしまうとは、皮肉なものです。

PHP動的言語であり、型チェックは実行時に行わなければいけないわけで、これは仕方ないのでしょうけども。


ただ、型を明確に書くということは、プログラムの可読性、保守性を上げることであり、わずかなルーチンが挟まるからと言って、型をかかないという選択をすることは愚かなことだと思います。

型を削除することで最適化をする暇があったら、もっと別のところを見直すべきです。

組み込み関数

PHPの各込み関数がどのようにC++になるのか見て行きましょう。

test9.php

<?php
$a = "hello";
$b = strstr($a,"he");

var_dump($a,$b);

// Dependencies
#include <runtime/ext/ext.h>

中略

/* preface starts */
extern CallInfo ci_;
/* preface finishes */
Variant pm_php$test9_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test9.php, pm_php$test9_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);
  Variant &v_b ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf8576bd5, "b")) : g->GV(b);

  v_a = NAMSTR(s_sse6f62137, "hello");
  {
    LINE(3,0);
    const Variant &tmp0((x_strstr(toString(v_a), NAMVAR(s_svs546f1cb9, "he"))));
    v_b.assignVal(tmp0);
  }
  LINE(5,(x_var_dump(2, v_a, Array(array_createvi(1, toVPOD(v_b))))));
  return true;
}

PHPの strstr は、 x_strstr という関数に変わりました。

$b = strstr($a,"he");
↓↓↓↓
const Variant &tmp0((x_strstr(toString(v_a), NAMVAR(s_svs546f1cb9, "he"))));
v_b.assignVal(tmp0);

x_strstrとは何でしょうか?

runtime/ext/profile/extprofile_string.h

inline Variant x_strstr(CStrRef haystack, CVarRef needle) {
  FUNCTION_INJECTION_BUILTIN(strstr);
  TAINT_OBSERVER(TAINT_BIT_MUTATED, TAINT_BIT_NONE);
  return f_strstr(haystack, needle);
}

さらに依存している f_strstrってなんなのよ。

runtime/ext/ext_string.cpp

Variant f_strstr(CStrRef haystack, CVarRef needle) {
  Variant ret = f_strpos(haystack, needle);
  if (same(ret, false)) {
    return false;
  }
  return haystack.substr(ret.toInt32());
}

ずいぶん回りくどい書き方ですね、、、haystack.substr って何?

なお、CStrRefは、 String型の typedef です。

runtime/base/types.h

typedef const String & CStrRef;

なので、探すべきは、String::substrです。

String::substr(haystack.substr) の実装を見てみましょう。

runtime/base/type_string.cpp

String String::substr(int start, int length /* = 0x7FFFFFFF */,
                      bool nullable /* = false */) const {
  int len = size();
  char *ret = string_substr(data(), len, start, length, nullable);
  if (ret) {
    return String(ret, len, AttachString);
  }
  return String();
}

またプロキシーですか。

string_substrって何?


runtime/base/zend/zend_string.cpp

char *string_substr(const char *s, int &len, int start, int length,
                    bool nullable) {
  ASSERT(s);
  if (string_substr_check(len, start, length)) {
    len = length;
    return string_duplicate(s + start, length);
  }
  len = 0;
  if (nullable) {
    return NULL;
  }
  return string_duplicate("", 0);
}

ルーチンながっ。

呼び出し履歴を見るとこんな感じですか?

なんかもっと大胆な最適化を期待したのですが、ちょっと残念です。

まだまだ早くなる可能性があるとプラス思考でいましょう。

x_strstr
↓
f_strstr
↓
String::substr
↓
string_substr
↓
まだ続くの?

無名関数

PHP5.3 から無名関数と束縛(キャプチャ)が入って、表現力が上がりました。

クロージャやカリー化もできるようになりました。

以前も、evalや create_functionで無理やりできていたのがより機能的になった感じですか。


さて、無名関数はどのように C++ に変換されているのでしょうか?


test10.php

<?php
$func = function($param1)
{
    return $param1 + 1;
};

$a = $func(10);
var_dump($a);

php/test10.php

/* preface starts */
extern CallInfo ci_;
extern CallInfo ci_08511066943069959013_1;
/* preface finishes */
/* SRC: test10.php line 3 */
Numeric f_08511066943069959013_1(void *extra, CVarRef v_param1) {
  FUNCTION_INJECTION(08511066943069959013_1);
  INTERCEPT_INJECTION("08511066943069959013_1", array_createvi(1, toVPOD(v_param1)), r);
  return (v_param1 + 1LL);
}
namespace hphp_impl_splitter {}

Variant ifa_08511066943069959013_1(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("08511066943069959013_1", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (f_08511066943069959013_1(extra, arg0));
}
Variant i_08511066943069959013_1(void *extra, CArrRef params) {
  return invoke_func_few_handler(extra, params, &ifa_08511066943069959013_1);
}

CallInfo ci_08511066943069959013_1((void*)&i_08511066943069959013_1, (void*)&ifa_08511066943069959013_1, 1, 0, 0x0000000000000000LL);
Variant pm_php$test10_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test10.php, pm_php$test10_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_func ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_sse63d8c2d, "func")) : g->GV(func);
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  {
    LINE(6,0);
    p_Closure tmp0((p_Closure(NEWOBJ(c_Closure)(&ci_08511066943069959013_1, NULL))));
    v_func = tmp0;
  }
  {
    const CallInfo *cit0;
    void *vt0;
    get_call_info_or_fail(cit0, vt0, v_func);
    LINE(8,0);
    Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 10LL)));
    v_a.assignVal(tmp1);
  }
  LINE(9,(x_var_dump(1, v_a)));
  return true;
}

無名関数は、静的な関数になっています。

そして、それを包むこむように p_Closure型のクラスがあります。

C++11から 無名関数(ラムダ式)がC++にも加わったので、hiphop の登場がもう少し遅ければ、C++11の無名関数(ラムダ式)になっていたような気もします。

(そして boost lambdaが使われなかったのは謎です。)



このあと説明するのですが、無名関数は、 今まで動作が謎だった、 ifa_* の名前を持つ関数を経由して実行されるようです。

Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 10LL)));
↓
↓
↓
Variant ifa_08511066943069959013_1(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("08511066943069959013_1", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (f_08511066943069959013_1(extra, arg0));
}
↓
↓
↓
Numeric f_08511066943069959013_1(void *extra, CVarRef v_param1) {
  FUNCTION_INJECTION(08511066943069959013_1);
  INTERCEPT_INJECTION("08511066943069959013_1", array_createvi(1, toVPOD(v_param1)), r);
  return (v_param1 + 1LL);
}

早速、呼び出しを順に見てみましょう。

  {
    LINE(6,0);
    p_Closure tmp0((p_Closure(NEWOBJ(c_Closure)(&ci_08511066943069959013_1, NULL))));
    v_func = tmp0;
  }

c_Closure は、 p_Closure の元になったものです。

typedef SmartObject<c_Closure> p_Closure; として定義されています。

引数で渡している ci_08511066943069959013_1 というのは何でしょうか?

これは、上の方で定義されている、CallInfo型です。

CallInfo ci_08511066943069959013_1((void*)&i_08511066943069959013_1, (void*)&ifa_08511066943069959013_1, 1, 0, 0x0000000000000000LL);

関数へのポインタを渡しているようです。



CallInfoはどのようなクラスなのでしょうか?

クラスの定義を見てみましょう。

runtime/base/builtin_functions.h

class CallInfo {
public:
  enum Flags {
    VarArgs         = 0x1,
    RefVarArgs      = 0x2,
    Method          = 0x4,
    StaticMethod    = 0x8,
    CallMagicMethod = 0x10, // Special flag for __call handler
    MixedVarArgs    = 0x20,
    Protected       = 0x40,
    Private         = 0x80
  };
  CallInfo(void *inv, void *invFa, int ac, int flags, int64 refs)
    : m_invoker(inv), m_invokerFewArgs(invFa),
      m_argCount(ac), m_flags(flags), m_refFlags(refs) {}
  void *m_invoker;
  void *m_invokerFewArgs; // remove in time

〜中略〜

  FuncInvoker getFunc() const { return (FuncInvoker)m_invoker; }
  FuncInvokerFewArgs getFuncFewArgs() const {
    return (FuncInvokerFewArgs)m_invokerFewArgs;
  }
  typedef Variant (*FuncInvoker0Args)(
    void*, int);
  typedef Variant (*FuncInvoker1Args)(
    void*, int, CVarRef);
  typedef Variant (*FuncInvoker2Args)(
    void*, int, CVarRef, CVarRef);
  typedef Variant (*FuncInvoker3Args)(
    void*, int, CVarRef, CVarRef, CVarRef);
  typedef Variant (*FuncInvoker4Args)(
    void*, int, CVarRef, CVarRef, CVarRef,
    CVarRef);

〜中略〜

  typedef Variant (*FuncInvoker10Args)(
    void*, int, CVarRef, CVarRef, CVarRef,
    CVarRef, CVarRef, CVarRef, CVarRef, CVarRef, CVarRef, CVarRef);
  FuncInvoker0Args getFunc0Args() const {
    return (FuncInvoker0Args)m_invokerFewArgs;
  }
  FuncInvoker1Args getFunc1Args() const {
    return (FuncInvoker1Args)m_invokerFewArgs;
  }
  FuncInvoker2Args getFunc2Args() const {
    return (FuncInvoker2Args)m_invokerFewArgs;
  }
  FuncInvoker3Args getFunc3Args() const {
    return (FuncInvoker3Args)m_invokerFewArgs;
  }
  FuncInvoker4Args getFunc4Args() const {
    return (FuncInvoker4Args)m_invokerFewArgs;
  }

〜中略〜

  FuncInvoker10Args getFunc10Args() const {
    return (FuncInvoker10Args)m_invokerFewArgs;
  }

〜略〜

};

どうやら、CallInfoは C++関数ポインタを経由してユーザの関数を呼び出しているみたいですね。

無名関数が呼び出される過程を詳細に見ていきます。

//無名関数で呼び出し関数をセットする
CallInfo ci_08511066943069959013_1((void*)&i_08511066943069959013_1, (void*)&ifa_08511066943069959013_1, 1, 0, 0x0000000000000000LL);
Variant pm_php$test10_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test10.php, pm_php$test10_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_func ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_sse63d8c2d, "func")) : g->GV(func);
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  //無名関数に割り当てる変数 v_func の初期化
  {
    LINE(6,0);
    p_Closure tmp0((p_Closure(NEWOBJ(c_Closure)(&ci_08511066943069959013_1, NULL))));
    v_func = tmp0;
  }
  //無名関数を実行し、結果を v_a に格納する
  {
    const CallInfo *cit0;
    void *vt0;
    get_call_info_or_fail(cit0, vt0, v_func); //cit0 に v_func を紐付ける.
    LINE(8,0);
    Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 10LL)));
    v_a.assignVal(tmp1);
  }
  //結果を var_dump して表示
  LINE(9,(x_var_dump(1, v_a)));
  return true;
}

どうも、この部分で無名関数を実行しているようですね。

CallInfo::getFunc1Args メソッド で、無名関数関数ポインタを経由して実行しています。

Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 10LL)));

上にもちょっと出てきましたが、 CallInfo::getFunc1Args メソッド は、関数ポインタを返す関数になっています。

  typedef Variant (*FuncInvoker1Args)(
    void*, int, CVarRef);
  FuncInvoker1Args getFunc1Args() const {
    return (FuncInvoker1Args)m_invokerFewArgs;
  }

実行させるところまでわかりしまた。

ただ、問題が一つあります。

それは、どのようにして、 cit0->getFunc1Args() に ifa_08511066943069959013_1 関数へのポインタを格納したかです。

無名関数の情報は v_func変数に格納されています。

今回、呼び出しに使った CallInfo* cit0 へどうやって情報を渡したのでしょうか?

鍵になるのは、 get_call_info_or_fail 関数です。

  //無名関数を実行し、結果を v_a に格納する
  {
    const CallInfo *cit0;
    void *vt0;
    get_call_info_or_fail(cit0, vt0, v_func); //両者を紐付ける.
    LINE(8,0);
    Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 10LL)));
    v_a.assignVal(tmp1);
  }



get_call_info_or_fail関数の詳細を見てみましょう。

runtime/base/builtin_functions.cpp

void get_call_info_or_fail(const CallInfo *&ci, void *&extra, CVarRef func) {
  if (UNLIKELY(!get_call_info(ci, extra, func))) {
    if (func.isObject()) {
      o_invoke_failed(
        func.objectForCall()->o_getClassName().c_str(),
        "__invoke", true);
    } else {
      throw InvalidFunctionCallException(func.toString().data());
    }
  }
}


しょっぱなでget_call_info という関数を読んでいます。

runtime/base/builtin_functions.cpp

bool get_call_info(const CallInfo *&ci, void *&extra, CVarRef func) {
  Variant::TypedValueAccessor tv_func = func.getTypedAccessor();
  if (Variant::GetAccessorType(tv_func) == KindOfObject) {
    ObjectData *d = Variant::GetObjectData(tv_func);
    ci = d->t___invokeCallInfoHelper(extra);
    return ci != NULL;
  }
  if (LIKELY(Variant::IsString(tv_func))) {
    StringData *sd = Variant::GetStringData(tv_func);
    return get_call_info(ci, extra, sd->data(), sd->hash());
  }
  return false;
}

なんか、それっぽいルーチンにきました。

第三引数 func つまり、v_func という無名関数関数情報をもっている変数を調べて、 第一引数 ci に格納しています。

こうやって、無名関数を CallInfo オブジェクトに関連付けて、実行しているのです。


しかし、結構回りくどい飛び出し方ですね。。。

C++11も出たことですし、C++11な lambdaを生成して欲しいものです。


束縛(キャプチャ)

無名関数変数を束縛(キャプチャ)することができます。

キャプチャはどのようにC++に変換されるのでしょうか?


test27.php

<?php
$cap = 10;
$func = function($a) use($cap)
{
    return $a + $cap + 1;
};

$c = $func(2); //2 + 10 + 1
var_dump($c);

php/test27.cpp

extern CallInfo ci_;
extern CallInfo ci_07296613028076329051_1;
FORWARD_DECLARE_CLASS(Closure$07296613028076329051_1);
class c_Closure$07296613028076329051_1 : public c_Closure {
  public:
  DECLARE_OBJECT_ALLOCATION_NO_SWEEP(c_Closure$07296613028076329051_1)
  Variant v_cap;

  c_Closure$07296613028076329051_1(const CallInfo *func, void *extra, CVarRef r_cap) : c_Closure(func, extra) {
    v_cap.assignVal(r_cap);
  }
};
/* preface finishes */
/* SRC: test27.php line 4 */
Numeric f_07296613028076329051_1(void *extra, CVarRef v_a) {
  FUNCTION_INJECTION(07296613028076329051_1);
  INTERCEPT_INJECTION("07296613028076329051_1", array_createvi(1, toVPOD(v_a)), r);
  c_Closure$07296613028076329051_1 *closure ATTRIBUTE_UNUSED = (c_Closure$07296613028076329051_1*)extra;
  Variant v_cap;

  v_cap.assignVal(closure->v_cap);
  return (v_a + (v_cap + 1LL));
}
namespace hphp_impl_splitter {}
Variant ifa_07296613028076329051_1(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("07296613028076329051_1", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (f_07296613028076329051_1(extra, arg0));
}
Variant i_07296613028076329051_1(void *extra, CArrRef params) {
  return invoke_func_few_handler(extra, params, &ifa_07296613028076329051_1);
}
CallInfo ci_07296613028076329051_1((void*)&i_07296613028076329051_1, (void*)&ifa_07296613028076329051_1, 1, 0, 0x0000000000000000LL);
IMPLEMENT_OBJECT_ALLOCATION_NO_DEFAULT_SWEEP(c_Closure$07296613028076329051_1)
Variant pm_php$test27_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test27.php, pm_php$test27_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_cap ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf0ead937, "cap")) : g->GV(cap);
  Variant &v_func ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_sse63d8c2d, "func")) : g->GV(func);
  Variant &v_c ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss5c5509b0, "c")) : g->GV(c);

  v_cap = 10LL;
  {
    LINE(7,0);
    p_Closure tmp0((p_Closure$07296613028076329051_1(NEWOBJ(c_Closure$07296613028076329051_1)(&ci_07296613028076329051_1, NULL, v_cap))));
    v_func = tmp0;
  }
  {
    const CallInfo *cit0;
    void *vt0;
    get_call_info_or_fail(cit0, vt0, v_func);
    LINE(9,0);
    Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 2LL)));
    v_c.assignVal(tmp1);
  }
  LINE(10,(x_var_dump(1, v_c)));
  return true;
}

上から眺めてみて、無名関数の時との違いを見てみましょう。


無名関数になかったクラスが生成されています。

FORWARD_DECLARE_CLASS(Closure$07296613028076329051_1);
class c_Closure$07296613028076329051_1 : public c_Closure {
  public:
  DECLARE_OBJECT_ALLOCATION_NO_SWEEP(c_Closure$07296613028076329051_1)
  Variant v_cap;

  c_Closure$07296613028076329051_1(const CallInfo *func, void *extra, CVarRef r_cap) : c_Closure(func, extra) {
    v_cap.assignVal(r_cap);
  }
};

無名関数が束縛する変数を管理するためのクラスだと思われます。

今回キャプチャするcap変数っぽい名前が見えますね。


次にルーチンを見て行きましょう。

v_cap という変数をp_Closure tmp0 の初期化時に渡しています。

これでこの変数の値を「コピーして保持」するみたいです。

Variant pm_php$test27_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test27.php, pm_php$test27_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_cap ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ssf0ead937, "cap")) : g->GV(cap);
  Variant &v_func ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_sse63d8c2d, "func")) : g->GV(func);
  Variant &v_c ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss5c5509b0, "c")) : g->GV(c);

  v_cap = 10LL;
  {
    LINE(7,0);
    p_Closure tmp0((p_Closure$07296613028076329051_1(NEWOBJ(c_Closure$07296613028076329051_1)(&ci_07296613028076329051_1, NULL, v_cap))));
    v_func = tmp0;
  }
  {
    const CallInfo *cit0;
    void *vt0;
    get_call_info_or_fail(cit0, vt0, v_func);
    LINE(9,0);
    Variant tmp1(((cit0->getFunc1Args())(vt0, 1, 2LL)));
    v_c.assignVal(tmp1);
  }
  LINE(10,(x_var_dump(1, v_c)));
  return true;
}

それ以外は、無名関数の時と同じに思えます。

get_call_info_or_fail(cit0, vt0, v_func); で、const CallInfo *cit0; に無名関数関数を関連付けて、 cit0->getFunc1Args() を経由して、呼び出します。

呼び出されると、ifa_* 系の関数である ifa_07296613028076329051_1 を経由して、無名関数本体が呼び出されます。

Variant ifa_07296613028076329051_1(void *extra, int count, INVOKE_FEW_ARGS_IMPL_ARGS) {
  if (UNLIKELY(count < 1)) throw_missing_arguments("07296613028076329051_1", count+1);
  CVarRef arg0(UNLIKELY(count <= 0) ? null_variant : a0);
  return (f_07296613028076329051_1(extra, arg0));
}


↓

Numeric f_07296613028076329051_1(void *extra, CVarRef v_a) {
  FUNCTION_INJECTION(07296613028076329051_1);
  INTERCEPT_INJECTION("07296613028076329051_1", array_createvi(1, toVPOD(v_a)), r);
  c_Closure$07296613028076329051_1 *closure ATTRIBUTE_UNUSED = (c_Closure$07296613028076329051_1*)extra;
  Variant v_cap;

  v_cap.assignVal(closure->v_cap);
  return (v_a + (v_cap + 1LL));
}

この時、 void *extra には、キャプチャしたcap変数の情報をもつ クラスのインスタンスが入ります。

それにより、キャプチャした本数に無名関数がアクセスできます。


やっと、関数が終わりました。

まだ眠くないですか?

眠くないなら、次は制御文を見て行きましょう。

if文

制御文の代表格 IF文を見て行きましょう。

test18.php

<?php
$a = 10;
if ($a == 10 ){
  echo "10"; 
}
else
{
  echo "else!"; 
}

Variant pm_php$test18_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test18.php, pm_php$test18_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  v_a = 10LL;
  if (equal(v_a, 10LL)) {
    {
      echo(NAMSTR(s_ssa4ae39ed, "10"));
    }
  }
  else {
    {
      echo(NAMSTR(s_ss2dcad959, "else!"));
    }
  }
  return true;

これは、綺麗に C++のIF文になりました。

文句の言いようがありません。

PHPの == 比較演算子は equal関数呼び出しに変わりました。

PHPの == 比較演算子ってクセがあるので、単純な C++ の ==比較演算子変換できないのも無理はありません。


===演算子

PHPdisる人が毎回毎回標的にあげてきて、その人気に嫉妬したくなる === 比較演算子を見てきます。

test19.php

<?php
$a = 10;
if ($a === 10 ){
  echo "10"; 
}
else
{
  echo "else!"; 
}

Variant pm_php$test19_php(bool incOnce, LVariableTable* variables, Globals *globals) {
  PSEUDOMAIN_INJECTION(run_init::test19.php, pm_php$test19_php);
  LVariableTable *gVariables ATTRIBUTE_UNUSED = (LVariableTable *)g;
  Variant &v_a ATTRIBUTE_UNUSED = (variables != gVariables) ? variables->get(NAMSTR(s_ss27b9150a, "a")) : g->GV(a);

  v_a = 10LL;
  if (same(v_a, 10LL)) {
    {
      echo(NAMSTR(s_ssa4ae39ed, "10"));
    }
  }
  else {
    {
      echo(NAMSTR(s_ss2dcad959, "else!"));
    }
  }
  return true;
}

見ての通りです。

PHPの ===比較演算子は、same関数呼び出しに変わりました。

なんか、あっけないほど簡単ですね。


PHPhiphop php
==equal
===same

後編に続く

記事が長すぎて、プレビューでは問題ないのに、投稿したときにぶった切られて切れてしまうみたいです。

はてなダイアリーの限界に挑んでいるんでしょうか。

hiphop php でPHPからジェネレートされたC++コードを読んでみよう(後編)に続きます。

JPzentaiJPzentai 2011/12/26 15:59 試します〜

T4T4 2012/10/11 18:04 読み応えある素晴らしい記事でした。

オカピーオカピー 2013/07/27 11:34 これは素晴らしい!
PHPとC++をここまで完璧に理解できている人は
なかなかいない!がんばって!

ひまわり ゴッホひまわり ゴッホ 2014/05/17 11:07 比較演算子ってクセがあるので、単純な C++ の ==比較演算子 に変換できないのも無理はありません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証