HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

PHPの三項演算子の注意すべき挙動

PHPで以下のようなコードを書いていてしばらくはまっていたのでメモ。

<?php

function a($a) {
    printf("%s\n",
        isset($a["A"]["B"]) ?
            $a["A"]["B"] :
            isset($a["AA"]["BB"]) ?
                $a["AA"]["BB"] :
                "CCC");
}

$a = array();
$a["A"]["B"] = "C";
a($a); // "C" ?

$a = array();
$a["AA"]["BB"] = "CC";
a($a); // "CC"

$a = array();
a($a); // "CCC"

実行結果は以下の通り。

PHP Notice:  Undefined index: AA in boo.php on line 8

CC
CCC

8行目でNoticeが出ることがある。8行目は

                $a["AA"]["BB"] :

の部分‥7行目でissetでチェックしているはずだが‥

ググってみると、以下のようなものが見つかった。

PHP: 比較演算子 - Manual

曰く

注意:
三項演算子を “積み重ねて” 使用することは避けましょう。 ひとつの文の中で複数の三項演算子を使用した際の PHP の振る舞いは、 少々わかりにくいものです。

例として挙がっているのが以下のコード。

<?php
echo (true?'true':false?'t':'f');

これはPHPでは

<?php
echo((true ? 'true' : false) ? 't' : 'f');

と評価され、’t' が出力されると。

‥ってこんなん分かるかいっ‥ってことで、各言語で試してみた。

PHP

CentOS 7のPHP 5.4.16

<?php
printf("%s\n", (true ? 'true' : false ? 't' : 'f'));
printf("%s\n", (false ? 'false' : true ? 't' : 'f'));
printf("%s\n", (false ? 'false' : false ? 't' : 'f'));

実行結果

t
t
f

Java

CentOS 7のOpenJDK 1.8.0_121

public class Hoge {
    public static void main(String[] args) {
        System.out.println(true ? "true" : false ? "t" : "f");
        System.out.println(false ? "false" : true ? "t" : "f");
        System.out.println(false ? "false" : false ? "t" : "f");
    }
}

実行結果

true
t
f

C

CentOS 7のgcc バージョン 4.8.5

#include <stdio.h>
#include <stdbool.h>

void main(int argc, char** argv) {
    printf("%s\n", true ? "true" : false ? "t" : "f");
    printf("%s\n", false ? "false" : true ? "t" : "f");
    printf("%s\n", false ? "false" : false ? "t" : "f");
}

実行結果

true
t
f

C++

CentOS 7のgcc バージョン 4.8.5

#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    cout << (true ? "true" : false ? "t" : "f") << endl;
    cout << ("%s\n", false ? "false" : true ? "t" : "f") << endl;
    cout << ("%s\n", false ? "false" : false ? "t" : "f") << endl;
    return 0;
}

実行結果

true
t
f

Ruby

CentOS 7のruby 2.0.0p648

printf("%s\n", true ? "true" : false ? "t" : "f");
printf("%s\n", false ? "false" : true ? "t" : "f");
printf("%s\n", false ? "false" : false ? "t" : "f");

実行結果

true
t
f

Python

CentOS 7のPython 2.7.5

print('true' if True else 't' if False else 'f');
print('false' if False else 't' if True else 'f');
print('false' if False else 't' if False else 'f');

実行結果

true
t
f

参考

pythonで三項演算子のネスト - Qiita

結局‥

冒頭のコードは

<?php

function a($a) {
    printf("%s\n",
        isset($a["A"]["B"]) ?
            $a["A"]["B"] :
            (isset($a["AA"]["BB"]) ?
                $a["AA"]["BB"] :
                "CCC"));
}

$a = array();
$a["A"]["B"] = "C";
a($a); // "C"

$a = array();
$a["AA"]["BB"] = "CC";
a($a); // "CC"

$a = array();
a($a); // "CCC"

のように括弧を付けて逃げましたとさ。やれやれ。

Apache TikaのPDFファイルテキスト抽出で遊んでみる

ということで、サイトのParser APIを追いかけてコードを組み立ててみたメモ。

環境は、CentOS 7(VM)上のOpenJDK 1.8.0_111。

PDFファイルからのテキスト抽出

以下のようなPDFファイルを使う。

f:id:hhelibex:20170227225332p:plain

PDFファイルからのテキスト抽出にはorg.apache.tika.parser.pdf.PDFParserクラスを使う。

最低限のコードは以下のような感じ。

import java.io.*;
import java.util.*;

import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.pdf.PDFParser;
import org.apache.tika.sax.BodyContentHandler;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class ParserSample {
    public static void main(String[] args) {
        Parser parser = new PDFParser();

        for (String arg : args) {
            ContentHandler handler = new BodyContentHandler();
            try (InputStream in = new FileInputStream(arg)) {
                Metadata metadata = new Metadata();
                ParseContext context = new ParseContext();
                try {
                    parser.parse(in, handler, metadata, context);
                } catch (TikaException | IOException | SAXException e) {
                    e.printStackTrace();
                }

                List<String> list = new ArrayList<>(Arrays.asList(handler.toString().trim().split("\\s")));
                List<String> names = new ArrayList<>(Arrays.asList(metadata.names()));
                Collections.sort(names);
                for (String name : names) {
                    System.out.printf("%-48s = %s%n", name, metadata.get(name));
                }
                System.out.println();
                System.out.println(list);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

これを実行すると、出力として以下のようなものが得られる。

Author                                           = hhelibex
Content-Type                                     = application/pdf
Creation-Date                                    = 2017-02-27T11:04:23Z
Last-Modified                                    = 2017-02-27T11:04:23Z
Last-Save-Date                                   = 2017-02-27T11:04:23Z
access_permission:assemble_document              = true
access_permission:can_modify                     = true
access_permission:can_print                      = true
access_permission:can_print_degraded             = true
access_permission:extract_content                = true
access_permission:extract_for_accessibility      = true
access_permission:fill_in_form                   = true
access_permission:modify_annotations             = true
created                                          = Mon Feb 27 20:04:23 JST 2017
creator                                          = hhelibex
date                                             = 2017-02-27T11:04:23Z
dc:creator                                       = hhelibex
dc:format                                        = application/pdf; version=1.4
dc:title                                         = Apache Tikaのテスト用
dcterms:created                                  = 2017-02-27T11:04:23Z
dcterms:modified                                 = 2017-02-27T11:04:23Z
meta:author                                      = hhelibex
meta:creation-date                               = 2017-02-27T11:04:23Z
meta:save-date                                   = 2017-02-27T11:04:23Z
modified                                         = 2017-02-27T11:04:23Z
pdf:PDFVersion                                   = 1.4
pdf:docinfo:created                              = 2017-02-27T11:04:23Z
pdf:docinfo:creator                              = hhelibex
pdf:docinfo:creator_tool                         = MicrosoftR Office ExcelR 2007
pdf:docinfo:modified                             = 2017-02-27T11:04:23Z
pdf:docinfo:producer                             = MicrosoftR Office ExcelR 2007
pdf:docinfo:title                                = Apache Tikaのテスト用
pdf:encrypted                                    = false
pdfa:PDFVersion                                  = A-1b
pdfaid:conformance                               = B
pdfaid:part                                      = 1
producer                                         = MicrosoftR Office ExcelR 2007
title                                            = Apache Tikaのテスト用
xmp:CreatorTool                                  = MicrosoftR Office ExcelR 2007
xmpMM:DocumentID                                 = uuid:72AB1A36-B534-4714-90E1-7640E90E7083
xmpTPg:NPages                                    = 1

[ヘッダ1, ヘッダ2, あ, ア, 阿, い, イ, 伊, う, ウ, 宇, え, エ, 江, お, オ, 尾]

PDFファイルだと「pdf:」で始まる名前を使ってMetadataから情報を取ればよい感じか。

コンテンツの方は、handler.toString()で得られる文字列が抽出結果になっているので、あとは煮るなり焼くなりすればいい。

汎用的にしてみる

実は、上記のことはorg.apache.tika.parser.AutoDetectParserを使っても実現できる。

ファイルタイプの検出に少し時間が掛かるのだが、PDFファイルだけではなくほかのファイルのテキストも同じコードで抽出したいという場合には有用だろう。

コードはほぼ同じになるが、以下のような感じ。

import java.io.*;
import java.util.*;

import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
//import org.apache.tika.parser.pdf.PDFParser;
import org.apache.tika.sax.BodyContentHandler;

import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class ParserSample {
    public static void main(String[] args) {
//      Parser parser = new PDFParser();
        Parser parser = new AutoDetectParser();

        for (String arg : args) {
            ContentHandler handler = new BodyContentHandler();
            try (InputStream in = new FileInputStream(arg)) {
                Metadata metadata = new Metadata();
                ParseContext context = new ParseContext();
                try {
                    parser.parse(in, handler, metadata, context);
                } catch (TikaException | IOException | SAXException e) {
                    e.printStackTrace();
                }

                List<String> list = new ArrayList<>(Arrays.asList(handler.toString().trim().split("\\s")));
                List<String> names = new ArrayList<>(Arrays.asList(metadata.names()));
                Collections.sort(names);
                for (String name : names) {
                    System.out.printf("%-48s = %s%n", name, metadata.get(name));
                }
                System.out.println();
                System.out.println(list);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

試しに、先に使ったPDFファイルの元であるExcelファイルを食わせてみる。

Application-Name                                 = Microsoft Excel
Author                                           = hhelibex
Company                                          = 
Content-Type                                     = application/vnd.ms-excel
Creation-Date                                    = 2017-02-27T10:23:34Z
Last-Author                                      = hhelibex
Last-Modified                                    = 2017-02-27T10:31:13Z
Last-Save-Date                                   = 2017-02-27T10:31:13Z
X-Parsed-By                                      = org.apache.tika.parser.DefaultParser
creator                                          = hhelibex
date                                             = 2017-02-27T10:31:13Z
dc:creator                                       = hhelibex
dc:title                                         = Apache Tikaのテスト用
dcterms:created                                  = 2017-02-27T10:23:34Z
dcterms:modified                                 = 2017-02-27T10:31:13Z
extended-properties:Application                  = Microsoft Excel
extended-properties:Company                      = 
meta:author                                      = hhelibex
meta:creation-date                               = 2017-02-27T10:23:34Z
meta:last-author                                 = hhelibex
meta:save-date                                   = 2017-02-27T10:31:13Z
modified                                         = 2017-02-27T10:31:13Z
title                                            = Apache Tikaのテスト用

[Sheet1, , , ヘッダ1, ヘッダ2, , あ, ア, 阿, , い, イ, 伊, , う, ウ, 宇, , え, エ, 江, , お, オ, 尾, , , Sheet2, , , , , Sheet3]

PDFファイルの場合も同様だが、AutoDetectParserを使うと「X-Parsed-By」がMetadataに追加されている。

シフト演算とMath.pow(2, n)と(+Math.pow(2, n)の怪)

自分が時々やらかしてしまうのでメモ。

環境は、CentOS 7(VM)上のOpenJDK 1.8.0_111。

2のべき乗(整数値)が欲しい場合はシフト演算

2のべき乗(整数値)が欲しいときに時々やらかしてしまうのが、以下のようなコードを書いてしまうこと。

for (int i = 0; i < 63; ++i) {
    long tmp = (long)Math.pow(2, i);
}

基数が2以外だったり指数が整数でなかったりdouble値が欲しかったりlongのMAX_VALUEを超える場合だったり、そんな場合は仕方がないのだが、2のべき乗(整数値)が欲しい場合はシフト演算の方が断然速い。

for (int i = 0; i < 63; ++i) {
    long tmp = 1L << i;
}

どれくらい速いのか、実際に計ってみた。

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        long S;
        long G;

        int cnt;

        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();

        System.out.print(n);

        S = System.currentTimeMillis();
        test1(n, m);
        G = System.currentTimeMillis();
        System.out.print("," + (G - S));

        S = System.currentTimeMillis();
        test2(n, m);
        G = System.currentTimeMillis();
        System.out.print("," + (G - S));

        S = System.currentTimeMillis();
        test3(n, m);
        G = System.currentTimeMillis();
        System.out.print("," + (G - S));

        System.out.println();
    }
    private static void test1(int n, int m) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 63; ++j) {
                long tmp = 1L << j;
            }
        }
    }
    private static void test2(int n, int m) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 63; ++j) {
                long tmp = (long)Math.pow(2, j);
            }
        }
    }
    private static void test3(int n, int m) {
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < 63; ++j) {
                long tmp = (long)Math.pow(2, m);
            }
        }
    }
}

test1が「Math.pow(2, n)」、test2が「シフト演算」を使ったケース。

test3は後で使うので後述。

実行は以下のような感じ。

for i in 100 250 500 1000 2500 5000 10000 25000 50000 100000 250000 500000 1000000 ; do
    echo $i 62 | java Main
done

実行結果のうち、test1とtest2を抜き出したものは以下の通り。単位はミリ秒。

n test1 test2
100 0 1
250 0 2
500 0 3
1000 1 5
2500 3 16
5000 5 30
10000 9 54
25000 10 121
50000 12 233
100000 13 458
250000 21 1142
500000 31 2260
1000000 54 4513

f:id:hhelibex:20161220232156p:plain

全然違う。

Math.pow(2, n)の怪

検証している最中に妙なことに気付いたのでついでにメモ。

上記プログラムのtest1とtest3を使う。

test3のポイントは、「Math.pow(2, m)」とループの最中に変わることがない値を指数に指定していること。

実行結果のうち、test1とtest3を抜き出してみる。単位はミリ秒。

n test1 test3
100 0 0
250 0 1
500 0 3
1000 1 8
2500 3 18
5000 5 31
10000 9 37
25000 10 37
50000 12 40
100000 13 45
250000 21 59
500000 31 83
1000000 54 130

f:id:hhelibex:20161220232701p:plain

それでもシフト演算の方が速いのだが、test2との違いは何だろう‥ソースを追おうにもnativeメソッドだし‥謎だ‥

rangeによるrangeの違い

Pythonのコードをちゃんと書いたことはないのだけど、ある理由でPythonのコードを読んでいて、ふと気になって調べたメモ。

結論から言うと、rangeで範囲を指定したときに列挙される値が、プログラム言語や、同じ言語でも書き方によって違うのだな、と。

実行環境

Python

以下のコードを実行する。

  • range.py
for i in range(1, 2):
    print(i)

実行結果。

1

つまり、for (i = 1; i < 2; ++i)(疑似コード)って書くのと一緒。

PHP

以下のコードを実行する。

<?php
foreach (range(1, 2) as $i) {
    printf("%d\n", $i);
}

実行結果。

1
2

つまり、for (i = 1; i <= 2; ++i)(疑似コード)って書くのと一緒。

Java

ついでなので調べてみたら、Java 8からjava.util.stream.IntStreamというのが追加されていた。

IntStreamには、range(int, int)rangeClosed(int, int)の2種類がある。

forEachメソッドのところは適当にググって真似てみた(謎)。

import java.util.stream.IntStream;

public class Range {
    public static void main(String[] args) {
        IntStream.range(1, 2).forEach(i -> {
            System.out.println(i);
        });
    }
}

実行結果。

1

こっちはPython式。

import java.util.stream.IntStream;

public class RangeClosed {
    public static void main(String[] args) {
        IntStream.rangeClosed(1, 2).forEach(i -> {
            System.out.println(i);
        });
    }
}

実行結果。

1
2

こっちはPHP式。

Ruby

さらについで。Rubyのプログラムもちゃんと書いたことはないのでググって調べてみた。

  • range.rb
for i in 1...2 do
    print(i, "\n")
end

または、

for i in Range.new(1, 2, true) do
    print(i, "\n")
end

実行結果。

1

Python式。

  • rangeClosed.rb
for i in 1..2 do
    print(i, "\n")
end

または、

for i in Range.new(1, 2) do
    print(i, "\n")
end

実行結果。

1
2

PHP式。

まとめると・・・

言語 プログラムの記述例 実際の範囲
Python for i in range(1, 100): 1 <= i < 100
PHP foreach (range(1, 100) as $i) 1 <= $i <= 100
Java IntStream.range(1, 100).forEach(i (略) 1 <= i < 100
IntStream.rangeClosed(1, 100).forEach(i (略) 1 <= i <= 100
Ruby for i in 1..100 do 1 <= i <= 100
for i in Range.new(1, 100) do 1 <= i <= 100
for i in 1...100 do 1 <= i < 100
for i in Range.new(1, 100, true) do 1 <= i < 100

QueueやStackとして使うなら‥LinkedList vs ArrayDeque

頭がJava 1.4で止まっているプログラマの呟き(謎)‥

LinkedListをQueueとして使ったあるプログラム(何)を書いていて、QueueやStackとして使うならLinkedListよりArrayDequeがお勧めとアドバイスをもらったので、軽く検証してみた。

事前準備

ソースコードは以下のような感じ。

  • LinkedListAsQueue.java
import java.util.LinkedList;
import java.util.Queue;

public class LinkedListAsQueueTest {
    public static void main(String[] args) {
        Queue<String> q = new LinkedList<>();

        long S = System.currentTimeMillis();
        while (true) {
            q.offer("a");
            long E = System.currentTimeMillis();
            System.err.println("q=" + q.size() + ", " + (E - S) + "ms");
        }
    }
}
  • ArrayDequeAsQueue.java
import java.util.ArrayDeque;
import java.util.Queue;

public class ArrayDequeAsQueueTest {
    public static void main(String[] args) {
        Queue<String> q = new ArrayDeque<>();

        long S = System.currentTimeMillis();
        while (true) {
            q.offer("a");
            long E = System.currentTimeMillis();
            System.err.println("q=" + q.size() + ", " + (E - S) + "ms");
        }
    }
}
  • LinkedListAsStack.java
import java.util.LinkedList;
import java.util.Deque;

public class LinkedListAsStackTest {
    public static void main(String[] args) {
        Deque<String> q = new LinkedList<>();

        long S = System.currentTimeMillis();
        while (true) {
            q.push("a");
            long E = System.currentTimeMillis();
            System.err.println("q=" + q.size() + ", " + (E - S) + "ms");
        }
    }
}
  • ArrayDequeAsStack.java
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeAsStackTest {
    public static void main(String[] args) {
        Deque<String> q = new ArrayDeque<>();

        long S = System.currentTimeMillis();
        while (true) {
            q.push("a");
            long E = System.currentTimeMillis();
            System.err.println("q=" + q.size() + ", " + (E - S) + "ms");
        }
    }
}

XxxAsStack.javaの方はDequeなので末尾からpush/popする使い方もありだが、わざと先頭に要素を追加する実装にしてある。

実行

長時間待つのが面倒なので、最大ヒープサイズを小さくして実行する。

$ java -Xmx8m <ClassName>

これでOutOfMemoryErrorが出るまで待って最終行を記録、ということを5回ずつ繰り返す。

結果

  • LinkedListAsQueue
q=328077, 6279ms
q=328112, 6165ms
q=328077, 6187ms
q=328077, 6463ms
q=328122, 6526ms
  • ArrayDequeAsQueue
q=524287, 6834ms
q=524287, 6766ms
q=524287, 6697ms
q=524287, 6732ms
q=524287, 6460ms
  • LinkedListAsStack
q=328111, 6309ms
q=328076, 6290ms
q=328085, 6378ms
q=328076, 6134ms
q=328076, 6326ms
  • ArrayDequeAsStack
q=524287, 6769ms
q=524287, 6576ms
q=524287, 6545ms
q=524287, 7081ms
q=524287, 6917ms

それぞれの平均をまとめると以下のような感じ。

要素の数 時間(ms) 要素の数/時間(ms)
LinkedListAsQueue 328093.0 6324.0 51.881
ArrayDequeAsQueue 524287.0 6697.8 78.277
LinkedListAsStack 328084.8 6287.4 52.181
ArrayDequeAsStack 524287.0 6777.6 77.356

確かに、メモリ使用量の観点からも速度の観点からもLinkedListよりArrayDequeの方がQueueやStackに向いていそう。速度的にはLinkedListの方が早いのかなと勝手に思っていたが違うのか‥ちょっとArrayDequeクラスのソースを覗いてみよう‥

文字列連結のパフォーマンス比較

そういえば計ったことなかったなぁ‥と思い立って、いくつかのパターンで計ってみたメモ。

実行環境は、自宅のXenServer上に載せた以下のVM環境。

ケース1:文字列連結演算子とダブルクォーテーションと

プログラムの全体は以下のような感じ。

n,test1,test2
<?php

for ($n = 10000; $n <= 50000; $n += 5000) {
set_time_limit(120);
    // test1
    $s1 = microtime(true);
    $str = '';
    for ($i = 0; $i < $n; ++$i) {
        $str = $str . 'a' . $i . 'b';
    }
    $e1 = microtime(true);

    // test2
    $s2 = microtime(true);
    $str = '';
    for ($i = 0; $i < $n; ++$i) {
        $str = "{$str}a{$i}b";
    }
    $e2 = microtime(true);

    printf("%d,%.3f,%.3f\n",
        $n, $e1 - $s1 , $e2 - $s2);
}

文字列を

        $str = $str . 'a' . $i . 'b';

で連結するか

        $str = "{$str}a{$i}b";

で連結するかの違い。

実行すると、以下のような出力が得られる。

n,test1,test2
10000,0.075,0.029
15000,0.228,0.065
20000,0.445,0.120
25000,0.873,0.199
30000,1.422,0.300
35000,2.168,0.451
40000,3.023,0.581
45000,2.802,0.739
50000,3.962,0.938

グラフにしてみるとこんな感じ。

f:id:hhelibex:20160609133252p:plain

ケース2:ダブルクォーテーションと複合演算子

プログラムの全体は以下のような感じ。

n,test2,test3,test4
<?php

for ($n = 10000; $n <= 100000; $n += 5000) {
set_time_limit(120);
    // test2
    $s2 = microtime(true);
    $str = '';
    for ($i = 0; $i < $n; ++$i) {
        $str = "{$str}a{$i}b";
    }
    $e2 = microtime(true);

    // test3
    $s3 = microtime(true);
    $str = '';
    for ($i = 0; $i < $n; ++$i) {
        $str .= 'a' . $i . 'b';
    }
    $e3 = microtime(true);

    // test4
    $s4 = microtime(true);
    $str = '';
    for ($i = 0; $i < $n; ++$i) {
        $str .= "a{$i}b";
    }
    $e4 = microtime(true);

    printf("%d,%.3f,%.3f,%.3f\n",
        $n, $e2 - $s2, $e3 - $s3, $e4 - $s4);
}

文字列を

        $str = "{$str}a{$i}b";
        $str .= 'a' . $i . 'b';
        $str .= "a{$i}b";

のように連結するという3パターン。1つ目は、ケース1で早かった方と同じ。

実行すると、以下のような出力が得られる。

n,test2,test3,test4
10000,0.016,0.001,0.001
15000,0.034,0.002,0.002
20000,0.069,0.003,0.003
25000,0.102,0.004,0.003
30000,0.149,0.004,0.004
35000,0.210,0.005,0.005
40000,0.281,0.006,0.005
45000,0.363,0.006,0.006
50000,0.455,0.007,0.007
55000,0.556,0.008,0.008
60000,0.668,0.009,0.008
65000,0.782,0.009,0.009
70000,0.916,0.010,0.009
75000,1.054,0.011,0.011
80000,1.212,0.011,0.011
85000,1.364,0.012,0.012
90000,1.534,0.013,0.012
95000,1.723,0.013,0.013
100000,1.915,0.014,0.014

‥圧倒的差である。一応グラフにしてみる。

f:id:hhelibex:20160609134310p:plain

test3のグラフは隠れて見えないが、test4の下に隠れている。

結局‥

  • ケース2の結果を見る限り、長い文字列$strが右辺に出てくるときの評価にコストがかかるのだろうか。
  • ケース1の結果から、同じ長い文字列が右辺に現れざるを得ない場合は、文字列連結演算子の処理の方がコストがかかるということか。

正直、ここまで差が出るとは思わなかったので、文字列連結処理を書くときには、これからは気を使うようにしようと思う。

PHPの識別子にアスキーコード0x7Fが使えることの検証

ということなので、実際に試してみた。

どうせなら1つのソースコードで完結させてやろうということで得られたコードが以下。

<?php

$name = "\x7F";
$expr = '$' . $name . '="b";var_dump($name, $' . $name . ');';
var_dump($expr);
eval($expr);

これを実行すると、以下のように出力される。(0x7Fの部分は□で表している)

string(27) "$□="b";var_dump($name, $□);"
string(1) "□"
string(1) "b"

ちなみに、識別子の先頭文字としては使えない数字を入れてみたコード:

<?php

$name = "1\x7F";
$expr = '$' . $name . '="b";var_dump($name, $' . $name . ');';
var_dump($expr);
eval($expr);

を実行すると、ちゃんとエラーになる。

string(29) "$1□="b";var_dump($name, $1□);"
PHP Parse error:  syntax error, unexpected T_LNUMBER, expecting T_VARIABLE or '$' in /home/kajino/foo.php(6) : eval()'d code on line 1