취약점 - serialize php array




PHP 배열을 저장하는 기본 방법(json_encode 대 serialize) (13)

JSON은 데이터를 백업하고 다른 머신이나 FTP를 통해 복원하려는 경우 더 좋습니다.

예를 들어 Windows 서버에 데이터를 저장하는 경우 serialize를 사용하고 FTP를 통해 다운로드 한 다음 Linux에서 복원하십시오. serialize는 문자열의 길이와 유니 코드를 저장하기 때문에 charcher 재 인코딩으로 인해 더 이상 작동하지 않습니다. > UTF-8 트랜스 코딩 일부 1 바이트 문자가 2 바이트 길이가되어 알고리즘이 중단 될 수 있습니다.

필자는 캐싱 목적으로 평면 파일에 다차원 연상 데이터 배열을 저장해야합니다. 가끔 내 웹 앱에서 JSON으로 변환 할 필요가있을 수도 있지만 대다수의 시간은 PHP에서 직접 배열을 사용하게 될 것입니다.

이 텍스트 파일에 배열을 JSON 또는 PHP 직렬 배열로 저장하는 것이 더 효율적입니까? 주위를 둘러 보았고 PHP (5.3)의 최신 버전에서 json_decode 가 실제로 unserialize 보다 빠릅니다.

저는 현재 JSON으로 배열을 저장하는쪽으로 기울어 있습니다. 필요한 경우 인간이 읽기 쉽다고 느끼기 때문에 PHP와 JavaScript 모두에서 약간의 노력으로 사용할 수 있습니다. 그리고 내가 읽은 것에서는 심지어 그렇게 될 수도 있습니다. 디코딩하기가 더 빠릅니다 (인코딩은 확실하지 않습니다).

누군가 함정에 대해 알고 있습니까? 누구나 두 가지 방법의 성능 이점을 보여줄 수있는 좋은 벤치 마크를 가지고 있습니까?


PHP에 대해 다른 직렬화 엔진을 제공하는 https://github.com/phadej/igbinary 관심이있을 수도 있습니다.

내 임의 / 임의의 '성능'수치, 64 비트 플랫폼에서 PHP 5.3.5를 사용하여 표시 :

JSON :

  • JSON 2.180496931076 초 인코딩
  • JSON이 9.8368630409241 초에 디코딩 됨
  • 직렬화 된 "문자열"크기 : 13993

기본 PHP :

  • PHP는 2.9125759601593 초에 직렬화 됨
  • PHP는 6.4348418712616 초 내에 직렬화되지 않았습니다.
  • 직렬화 된 "문자열"크기 : 20769

Igbinary :

  • 1.6099879741669 초에 직렬화 된 WIN igbinary
  • 4.7737920284271 초 내에 직렬화되지 않은 직렬화
  • WIN 에서 "문자열"을 직렬화 함 크기 : 4467

따라서 igbinary_serialize () 및 igbinary_unserialize ()가 빠르며 디스크 공간을 덜 사용합니다.

필자는 위와 같이 fillArray (0, 3) 코드를 사용했지만 배열 키를 더 긴 문자열로 만들었습니다.

igbinary는 PHP의 네이티브 serialize can (객체 등의 문제는 없음)과 동일한 데이터 유형을 저장할 수 있으며 원할 경우 세션 처리를 위해 PHP5.3을 사용할 수 있습니다.

http://ilia.ws/files/zendcon_2010_hidden_features.pdf - 특히 슬라이드 14/15/16도 참조하십시오.


THX -이 벤치 마크 코드 :

구성에 사용하는 배열에 대한 결과는 다음과 같습니다. JSON은 0.0031511783599854 초로 인코딩되었습니다.
PHP는 0.0037961006164551 초에 직렬화되었습니다.
json_encode()serialize() 보다 약 20.47 % 빠릅니다. JSON은 0.0070841312408447 초로 인코딩되었습니다.
PHP는 0.0035839080810547 초에 직렬화 됨
unserialize()json_encode() 보다 약 97.66 % 빠릅니다.

따라서 자신의 데이터로 테스트하십시오.


Y는 방금 serialize 된 json 인코딩 및 디코드를 테스트하고 크기를 더하면 저장된 문자열을 취합니다.

JSON encoded in 0.067085981369 seconds. Size (1277772)
PHP serialized in 0.12110209465 seconds. Size (1955548)
JSON decode in 0.22470498085 seconds
PHP serialized in 0.211947917938 seconds
json_encode() was roughly 80.52% faster than serialize()
unserialize() was roughly 6.02% faster than json_decode()
JSON string was roughly 53.04% smaller than Serialized string

JSON은 더 빨리 인코딩하고 결과는 더 작은 문자열이지만, unserialize는 문자열을 디코딩하는 것이 더 빠르다고 결론 지을 수 있습니다.


나는 작은 기준을 만들었다. 내 결과는 같았습니다. 하지만 디코딩 성능이 필요합니다. 위의 몇 사람처럼 json_decode 보다 unserialize 가 빠르다는 것을 알았습니다. unserializejson_decode 시간의 대략 60-70 %를 차지합니다. 따라서 결론은 매우 간단합니다. 인코딩에서 성능이 필요하면 json_encode 사용하고, 디코딩 할 때 성능이 필요할 때는 unserialize 사용하십시오. 두 가지 기능을 병합 할 수 없으므로 더 많은 성능이 필요한 부분을 선택해야합니다.

의사의 내 벤치 마크 :

  • 임의의 키와 값을 몇 개 사용하여 $ arr 배열을 정의하십시오.
  • x <100; x ++; serialize하고 json_encode $ arr의 array_rand
  • y <1000 인 경우; y ++; json_decode json 인코딩 된 문자열 - calc 시간
  • y <1000 인 경우; y ++; 직렬화 된 문자열의 직렬화 해제 - 계산 시간
  • 더 빨라진 결과를 에코하다.

평균 : unserialize는 json_decode의 4 배가 넘는 96 배의 승리를 기록했습니다. 2.5ms가 넘는 약 1.5ms의 avarage.


단순한 fyi - JSON과 같이 읽기 쉽고 이해하기 쉬운 데이터로 직렬화하고 싶지만 압축률과 성능이 향상되면 msgpack 확인해야합니다 msgpack


여기에서 결과를 확인하십시오 (JS 코드 상자에 PHP 코드를 삽입하는 해킹에 대해 사과드립니다).

http://jsfiddle.net/newms87/h3b0a0ha/embedded/result/

결과 : serialize()unserialize() 는 크기가 다양한 배열의 PHP 5.4에서 상당히 빠릅니다.

나는 json_encode와 serialize 그리고 json_decode와 unserialize를 비교하기위한 실제 데이터에 대한 테스트 스크립트를 만들었다. 테스트는 프로덕션 전자 상거래 사이트의 캐싱 시스템에서 실행되었습니다. 단순히 캐시에있는 데이터를 가져 와서 모든 데이터를 인 코드 / 디코드 (또는 serialize / unserialize)하는 시간을 테스트하여 쉽게 볼 수있는 테이블에 넣습니다.

PHP 5.4 공유 호스팅 서버에서 실행했습니다.

결과는 이러한 대용량 및 소규모 데이터 세트가 직렬화되고 비 직렬화되면 확실한 승자라는 결론을 내릴 수있었습니다. 특히 유스 케이스의 경우 json_decode와 unserialize가 캐싱 시스템에서 가장 중요합니다. Unserialize는 거의 유비쿼터스 수상자였습니다. 일반적으로 json_decode의 2 ~ 4 배 (때로는 6 ~ 7 배) 빠릅니다.

@ peter-bailey의 결과 차이점을 살펴 보는 것은 흥미로운 일입니다.

결과를 생성하는 데 사용되는 PHP 코드는 다음과 같습니다.

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

function _count_depth($array)
{
    $count     = 0;
    $max_depth = 0;
    foreach ($array as $a) {
        if (is_array($a)) {
            list($cnt, $depth) = _count_depth($a);
            $count += $cnt;
            $max_depth = max($max_depth, $depth);
        } else {
            $count++;
        }
    }

    return array(
        $count,
        $max_depth + 1,
    );
}

function run_test($file)
{
    $memory     = memory_get_usage();
    $test_array = unserialize(file_get_contents($file));
    $memory     = round((memory_get_usage() - $memory) / 1024, 2);

    if (empty($test_array) || !is_array($test_array)) {
        return;
    }

    list($count, $depth) = _count_depth($test_array);

    //JSON encode test
    $start            = microtime(true);
    $json_encoded     = json_encode($test_array);
    $json_encode_time = microtime(true) - $start;

    //JSON decode test
    $start = microtime(true);
    json_decode($json_encoded);
    $json_decode_time = microtime(true) - $start;

    //serialize test
    $start          = microtime(true);
    $serialized     = serialize($test_array);
    $serialize_time = microtime(true) - $start;

    //unserialize test
    $start = microtime(true);
    unserialize($serialized);
    $unserialize_time = microtime(true) - $start;

    return array(
        'Name'                   => basename($file),
        'json_encode() Time (s)' => $json_encode_time,
        'json_decode() Time (s)' => $json_decode_time,
        'serialize() Time (s)'   => $serialize_time,
        'unserialize() Time (s)' => $unserialize_time,
        'Elements'               => $count,
        'Memory (KB)'            => $memory,
        'Max Depth'              => $depth,
        'json_encode() Win'      => ($json_encode_time > 0 && $json_encode_time < $serialize_time) ? number_format(($serialize_time / $json_encode_time - 1) * 100, 2) : '',
        'serialize() Win'        => ($serialize_time > 0 && $serialize_time < $json_encode_time) ? number_format(($json_encode_time / $serialize_time - 1) * 100, 2) : '',
        'json_decode() Win'      => ($json_decode_time > 0 && $json_decode_time < $serialize_time) ? number_format(($serialize_time / $json_decode_time - 1) * 100, 2) : '',
        'unserialize() Win'      => ($unserialize_time > 0 && $unserialize_time < $json_decode_time) ? number_format(($json_decode_time / $unserialize_time - 1) * 100, 2) : '',
    );
}

$files = glob(dirname(__FILE__) . '/system/cache/*');

$data = array();

foreach ($files as $file) {
    if (is_file($file)) {
        $result = run_test($file);

        if ($result) {
            $data[] = $result;
        }
    }
}

uasort($data, function ($a, $b) {
    return $a['Memory (KB)'] < $b['Memory (KB)'];
});

$fields = array_keys($data[0]);
?>

<table>
    <thead>
    <tr>
        <?php foreach ($fields as $f) { ?>
            <td style="text-align: center; border:1px solid black;padding: 4px 8px;font-weight:bold;font-size:1.1em"><?= $f; ?></td>
        <?php } ?>
    </tr>
    </thead>

    <tbody>
    <?php foreach ($data as $d) { ?>
        <tr>
            <?php foreach ($d as $key => $value) { ?>
                <?php $is_win = strpos($key, 'Win'); ?>
                <?php $color = ($is_win && $value) ? 'color: green;font-weight:bold;' : ''; ?>
                <td style="text-align: center; vertical-align: middle; padding: 3px 6px; border: 1px solid gray; <?= $color; ?>"><?= $value . (($is_win && $value) ? '%' : ''); ?></td>
            <?php } ?>
        </tr>
    <?php } ?>
    </tbody>
</table>

우선 순위에 따라 다릅니다.

성능이 절대 주행 특성이라면, 반드시 가장 빠른 것을 사용하십시오. 선택을하기 전에 차이점을 완전히 이해했는지 확인하십시오.

  • serialize() 와 달리, UTF-8 문자를 그대로 유지하려면 추가 매개 변수를 추가해야합니다. json_encode($array, JSON_UNESCAPED_UNICODE) (그렇지 않으면 UTF-8 문자를 유니 코드 이스케이프 시퀀스로 변환합니다).
  • JSON은 객체의 원래 클래스가 무엇인지 기억하지 않습니다 (항상 stdClass의 인스턴스로 복원 됨).
  • JSON과 함께 __sleep()__wakeup() 을 활용할 수는 없습니다.
  • 기본적으로 public 속성 만 JSON으로 serialize됩니다. ( PHP>=5.4 JsonSerializable 을 구현 JsonSerializable 동작을 변경할 수 있습니다).
  • JSON은 더 휴대 가능합니다.

그리고 내가 생각할 수없는 몇 가지 다른 점이있을 것입니다.

두 가지를 비교하는 간단한 속도 테스트

<?php

ini_set('display_errors', 1);
error_reporting(E_ALL);

// Make a big, honkin test array
// You may need to adjust this depth to avoid memory limit errors
$testArray = fillArray(0, 5);

// Time json encoding
$start = microtime(true);
json_encode($testArray);
$jsonTime = microtime(true) - $start;
echo "JSON encoded in $jsonTime seconds\n";

// Time serialization
$start = microtime(true);
serialize($testArray);
$serializeTime = microtime(true) - $start;
echo "PHP serialized in $serializeTime seconds\n";

// Compare them
if ($jsonTime < $serializeTime) {
    printf("json_encode() was roughly %01.2f%% faster than serialize()\n", ($serializeTime / $jsonTime - 1) * 100);
}
else if ($serializeTime < $jsonTime ) {
    printf("serialize() was roughly %01.2f%% faster than json_encode()\n", ($jsonTime / $serializeTime - 1) * 100);
} else {
    echo "Impossible!\n";
}

function fillArray( $depth, $max ) {
    static $seed;
    if (is_null($seed)) {
        $seed = array('a', 2, 'c', 4, 'e', 6, 'g', 8, 'i', 10);
    }
    if ($depth < $max) {
        $node = array();
        foreach ($seed as $key) {
            $node[$key] = fillArray($depth + 1, $max);
        }
        return $node;
    }
    return 'empty';
}

정말 좋은 주제이고 몇 가지 답변을 읽은 후에 주제에 대한 실험을 공유하고 싶습니다.

나는 "거대한"테이블이 데이타베이스에 대해 거의 언제나 질의를 받아야하는 유스 케이스를 가지고있다. 왜 그런지 묻지 않는다. 데이터베이스 캐싱 시스템은 다른 요청을 캐시하지 않으므로 적절하지 않습니다. 따라서 PHP 캐싱 시스템에 대해서 생각합니다.

나는 apcu 시도했으나 필요에 맞지 않았다.이 경우에는 메모리가 충분하지 않다. 다음 단계는 직렬화 된 파일에 캐시하는 것이 었습니다.

테이블에는 18 열이있는 14355 개의 항목이 있는데, 이들은 직렬화 된 캐시를 읽는 것에 대한 테스트와 통계입니다.

JSON :

모두 말씀 드렸듯이 json_encode / json_decode 의 주요 불편은 모든 것을 StdClass 인스턴스 (또는 Object)로 변환한다는 것입니다. 루프로 변환해야한다면 배열로 변환하면됩니다. 그렇다면 변형 시간이 길어질 것입니다.

평균 시간 : 780.2 ms; 메모리 사용 : 41.5MB; 캐시 파일 크기 : 3.8MB

Msgpack

@hutch는 msgpack을 언급 msgpack . 예쁜 웹 사이트. 우리가 시도해 볼까요?

평균 시간 : 497 ms; 메모리 사용 : 32MB; 캐시 파일 크기 : 2.8MB

더 좋지만 새로운 확장이 필요합니다. 때때로 두려워하는 사람들을 편집하는 중 ...

IgBinary

@GingerDog에 igbinary가 언급 https://github.com/phadej/igbinary . 파일 크기보다 읽기 성능이 중요하기 때문에 igbinary.compact_strings=Off 설정했습니다.

평균 시간 : 411.4 ms; 메모리 사용 : 36.75MB; 캐시 파일 크기 : 3.3MB

msg 패키지보다 좋습니다. 아직도이 컴파일 작업이 필요합니다.

serialize / unserialize

평균 시간 : 477.2 ms; 메모리 사용 : 36.25MB; 캐시 파일 크기 : 5.9MB

JSON보다 성능이 좋을수록 어레이가 더 커지고 느린 json_decode 가되지만 이미 새로운 것입니다.

이러한 외부 확장은 파일 크기를 좁히고 있으며 종이에 위대하게 보인다. 숫자는 거짓말하지 않습니다 *. 표준 PHP 함수로 얻을 수있는 결과와 거의 같은 결과를 얻을 수 있다면 확장 기능을 컴파일해야하는 이유는 무엇입니까?

우리는 또한 당신의 필요에 따라 다른 누군가와 다른 것을 선택할 것이라고 추론 할 수 있습니다.

  • IgBinary는 정말 훌륭하고 MsgPack보다 성능이 좋습니다.
  • Msgpack은 데이터를 압축하는 것이 좋습니다 (igbinary compact.string 옵션을 시도하지 않았 음에 유의하십시오).
  • 컴파일하고 싶지 않습니까? 표준을 사용하십시오.

그 중 하나를 선택하는 데 도움이되는 또 다른 직렬화 방법 비교입니다!

* PHPUnit 3.7.31, PHP 5.5.10으로 테스트 - 표준 하드 드라이브 및 구형 듀얼 코어 CPU로 디코딩 - 동일한 사용 사례 테스트 10 개에 대한 평균 수치, 통계가 다를 수 있음


직렬화처럼 보이는 이유는 다음 두 가지 이유 때문입니다.

  • 누군가는 unserialize가 json_decode보다 빠르며 'read'case가 'write'case보다 가능성이 높음을 지적했습니다.

  • 잘못된 UTF-8 문자로 된 문자열을 가지고있을 때 json_encode에 문제가있었습니다. 그렇게되면 문자열이 비어 결국 정보가 손실됩니다.


최종 결정을 내리기 전에 JSON 형식이 연관 배열에 대해 안전하지 않다는 점에 유의하십시오. json_decode() 는 대신 객체로 반환합니다.

$config = array(
    'Frodo'   => 'hobbit',
    'Gimli'   => 'dwarf',
    'Gandalf' => 'wizard',
    );
print_r($config);
print_r(json_decode(json_encode($config)));

출력은 다음과 같습니다.

Array
(
    [Frodo] => hobbit
    [Gimli] => dwarf
    [Gandalf] => wizard
)
stdClass Object
(
    [Frodo] => hobbit
    [Gimli] => dwarf
    [Gandalf] => wizard
)

필자는 꽤 복잡하고 가벼운 중첩 된 다중 해시 (문자열, NULL, 정수)가있는 모든 종류의 데이터를 테스트했으며 serialize / unserialize는 json_encode / json_decode보다 훨씬 빠릅니다.

json이 내 테스트에서 얻은 유일한 이점은 크기가 더 작은 '포장 된'크기라는 것입니다.

이것들은 PHP 5.3.3에서 수행됩니다. 자세한 내용을 원하면 알려주십시오.

다음은 테스트 결과이며 그 다음 코드가 생성됩니다. 테스트 데이터를 제공 할 수 없기 때문에 야생에서 나가게 할 수없는 정보가 있음을 알 수 있습니다.

JSON encoded in 2.23700618744 seconds
PHP serialized in 1.3434419632 seconds
JSON decoded in 4.0405561924 seconds
PHP unserialized in 1.39393305779 seconds

serialized size : 14549
json_encode size : 11520
serialize() was roughly 66.51% faster than json_encode()
unserialize() was roughly 189.87% faster than json_decode()
json_encode() string was roughly 26.29% smaller than serialize()

//  Time json encoding
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    json_encode( $test );
}
$jsonTime = microtime( true ) - $start;
echo "JSON encoded in $jsonTime seconds<br>";

//  Time serialization
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    serialize( $test );
}
$serializeTime = microtime( true ) - $start;
echo "PHP serialized in $serializeTime seconds<br>";

//  Time json decoding
$test2 = json_encode( $test );
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    json_decode( $test2 );
}
$jsonDecodeTime = microtime( true ) - $start;
echo "JSON decoded in $jsonDecodeTime seconds<br>";

//  Time deserialization
$test2 = serialize( $test );
$start = microtime( true );
for($i = 0; $i < 10000; $i++) {
    unserialize( $test2 );
}
$unserializeTime = microtime( true ) - $start;
echo "PHP unserialized in $unserializeTime seconds<br>";

$jsonSize = strlen(json_encode( $test ));
$phpSize = strlen(serialize( $test ));

echo "<p>serialized size : " . strlen(serialize( $test )) . "<br>";
echo "json_encode size : " . strlen(json_encode( $test )) . "<br></p>";

//  Compare them
if ( $jsonTime < $serializeTime )
{
    echo "json_encode() was roughly " . number_format( ($serializeTime / $jsonTime - 1 ) * 100, 2 ) . "% faster than serialize()";
}
else if ( $serializeTime < $jsonTime )
{
    echo "serialize() was roughly " . number_format( ($jsonTime / $serializeTime - 1 ) * 100, 2 ) . "% faster than json_encode()";
} else {
    echo 'Unpossible!';
}
    echo '<BR>';

//  Compare them
if ( $jsonDecodeTime < $unserializeTime )
{
    echo "json_decode() was roughly " . number_format( ($unserializeTime / $jsonDecodeTime - 1 ) * 100, 2 ) . "% faster than unserialize()";
}
else if ( $unserializeTime < $jsonDecodeTime )
{
    echo "unserialize() was roughly " . number_format( ($jsonDecodeTime / $unserializeTime - 1 ) * 100, 2 ) . "% faster than json_decode()";
} else {
    echo 'Unpossible!';
}
    echo '<BR>';
//  Compare them
if ( $jsonSize < $phpSize )
{
    echo "json_encode() string was roughly " . number_format( ($phpSize / $jsonSize - 1 ) * 100, 2 ) . "% smaller than serialize()";
}
else if ( $phpSize < $jsonSize )
{
    echo "serialize() string was roughly " . number_format( ($jsonSize / $phpSize - 1 ) * 100, 2 ) . "% smaller than json_encode()";
} else {
    echo 'Unpossible!';
}

JSON 은 PHP의 직렬화 형식보다 간단하고 빠르며 다음과 같은 경우를 제외 하고 사용해야합니다.

  • 깊게 중첩 된 배열을 저장하고 있습니다. json_decode() : "이 함수는 JSON 인코딩 된 데이터가 127 개 요소보다 깊을 경우 false를 반환합니다."
  • 올바른 클래스로 직렬화되지 않아야하는 객체를 저장하고 있습니다.
  • json_decode를 지원하지 않는 구 버전의 PHP와 상호 작용하고 있습니다.






serialization