Мне нужно сохранить многомерный ассоциативный массив данных в плоском файле для целей кеширования. Иногда мне приходилось переходить на JSON для использования в моем веб-приложении, но в большинстве случаев я буду использовать массив непосредственно в PHP.
Будет ли более эффективным хранить массив как JSON или как сериализованный массив PHP в этом текстовом файле? Я огляделся, и кажется, что в новейших версиях PHP (5.3) json_decode
на самом деле быстрее, чем unserialize
.
В настоящее время я склоняюсь к тому, чтобы хранить массив как JSON, поскольку я чувствую, что его легче читать человеком, если это необходимо, его можно использовать как в PHP, так и в JavaScript с очень небольшим усилием и из того, что я прочитал, может даже быть быстрее для декодирования (не уверен в кодировке, хотя).
Кто-нибудь знает какие-нибудь подводные камни? У кого-нибудь есть хорошие ориентиры, чтобы показать преимущества производительности любого из методов?
Зависит от ваших приоритетов.
Если производительность является вашей абсолютной характеристикой вождения, то, во что бы то ни стало, используйте самую быструю. Просто убедитесь, что у вас есть полное понимание различий, прежде чем сделать выбор.
json_encode()
преобразует символы UTF-8 в escape-последовательности Unicode, а serialize()
- нет. Примечание.. Чтобы оставить символы UTF-8 нетронутыми, вы можете использовать опцию JSON_UNESCAPED_UNICODE
с PHP 5.4.__sleep()
и __wakeup()
с помощью 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';
}
JSON проще и быстрее, чем формат сериализации PHP, и его следует использовать , если:
json_decode()
: "Эта функция вернет false, если закодированные данные JSON глубже 127 элементов".Я написал блогпост об этой теме: " Кэш большого массива: JSON, serialize или var_export?". В этом сообщении показано, что сериализация является лучшим выбором для массивов малых и больших размеров. Для очень больших массивов ( > 70 МБ) лучше выбрать JSON.
Вы также можете быть заинтересованы в https://github.com/phadej/igbinary - который предоставляет другой механизм сериализации для PHP.
Мои случайные/произвольные показатели производительности, использующие PHP 5.3.5 на 64-битной платформе, показывают:
JSON:
Нативный PHP:
Igbinary:
Итак, это быстрее для igbinary_serialize() и igbinary_unserialize() и использует меньше дискового пространства.
Я использовал код fillArray (0, 3), как указано выше, но сделал ключи массива более длинными.
igbinary может хранить те же типы данных, что и PHP native serialize (так что не проблема с объектами и т.д.), и вы можете сказать PHP5.3 использовать его для обработки сеанса, если вы этого пожелаете.
См. также http://ilia.ws/files/zendcon_2010_hidden_features.pdf - в частности слайды 14/15/16
Y просто проверял сериализованный и 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 быстрее декодирует строку.
Если вы кешируете информацию, которую вы в конечном итоге захотите "включить" в более поздний момент времени, вы можете попробовать использовать var_export. Таким образом, вы принимаете удар только в "сериализации", а не в "unserialize".
Я добавил тест, чтобы включить производительность несериализации. Вот цифры, которые я получил.
Serialize
JSON encoded in 2.5738489627838 seconds
PHP serialized in 5.2861361503601 seconds
Serialize: json_encode() was roughly 105.38% faster than serialize()
Unserialize
JSON decode in 10.915472984314 seconds
PHP unserialized in 7.6223039627075 seconds
Unserialize: unserialize() was roughly 43.20% faster than json_decode()
Так что json, кажется, быстрее для кодирования, но медленный в декодировании. Таким образом, это может зависеть от вашего приложения и того, что вы ожидаете делать больше всего.
Кажется, что сериализация - это тот, который я буду использовать по двум причинам:
Кто-то отметил, что unserialize быстрее, чем json_decode, и случай с "чтением" звучит более вероятным, чем "запись".
У меня были проблемы с json_encode при наличии строк с недопустимыми символами UTF-8. Когда это происходит, строка заканчивается пустым, что приводит к потере информации.
Действительно хорошая тема, и, прочитав несколько ответов, я хочу поделиться своими экспериментами по этому вопросу.
У меня есть прецедент, когда некоторая "огромная" таблица должна запрашиваться почти каждый раз, когда я разговариваю с базой данных (не спрашивайте, почему, просто факт). Система кэширования базы данных не подходит, поскольку она не будет кэшировать разные запросы, поэтому я, хотя, о системах кэширования php.
Я пробовал apcu
, но он не соответствовал потребностям, в этом случае память недостаточно надежна. Следующим шагом было кэширование в файл с сериализацией.
Таблица содержит 14355 записей с 18 столбцами, это мои тесты и статистика при чтении сериализованного кеша:
Как вы все сказали, основным неудобством с json_encode
/json_decode
является то, что он преобразует все в экземпляр StdClass
(или Object). Если вам нужно зацикливать его, преобразование его в массив - это то, что вы, вероятно, сделаете, и да, это увеличивает время преобразования
среднее время: 780,2 мс; использование памяти: 41,5 МБ; размер кеш файла: 3,8 МБ
@hutch упоминает msgpack. Довольно сайт. Попробуем попробовать?
среднее время: 497 мс; использование памяти: 32 МБ; размер кеш файла: 2,8 МБ
Это лучше, но требует нового расширения; компиляция иногда боится людей...
@GingerDog упоминает igbinary. Обратите внимание, что я установил igbinary.compact_strings=Off
, потому что мне больше нравится чтение результатов, чем размер файла.
среднее время: 411,4 мс; использование памяти: 36,75 МБ; размер кеш файла: 3,3 МБ
Лучше, чем пакет msg. Тем не менее, это требует компиляции тоже.
serialize
/<Т26 > среднее время: 477,2 мс; использование памяти: 36,25 МБ; размер кеш файла: 5.9MB
Чем выше производительность, чем JSON, тем больше массив, медленнее json_decode
, но вы уже новичок в этом.
Эти внешние расширения сужают размер файла и выглядят великолепно на бумаге. Числа не лежат *. Какой смысл компилировать расширение, если вы получите почти те же результаты, что и у стандартной функции PHP?
Мы также можем вывести, что в зависимости от ваших потребностей вы выберете что-то другое, чем кто-либо другой:
Чтобы это, другое сравнение методов сериализации поможет вам выбрать один!
* Протестировано с помощью PHPUnit 3.7.31, php 5.5.10 - только декодирование со стандартным жестким диском и старым двухъядерным процессором - среднее число на 10 одинаковых тестах использования, ваша статистика может отличаться
Я очень тщательно протестировал это на довольно сложном, мягко вложенном мульти-хеше со всеми видами данных в нем (string, NULL, integers), а 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!';
}
Я сделал небольшой ориентир. Мои результаты были одинаковыми. Но мне нужна производительность декодирования. Там, где я заметил, как и несколько человек выше, unserialize
быстрее, чем json_decode
. unserialize
занимает примерно 60-70% времени json_decode
. Таким образом, вывод довольно прост:
Если вам нужна производительность в кодировке, используйте json_encode
, когда вам требуется производительность при декодировании, используйте unserialize
. Поскольку вы не можете объединить две функции, вам нужно сделать выбор, где вам нужно больше производительности.
Мой тест в псевдо:
В avarage: unserialize выиграл 96 раз в 4 раза json_decode. С пропускной способностью примерно 1,5 мс за 2,5 мс.
Прежде чем принимать окончательное решение, имейте в виду, что формат 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
)
json_encode
было ассоциативным массивом, вы можете легко принудительно вернуть его обратно в массив следующим образом: $json = json_encode($some_assoc_array); $back_to_array = (array)json_decode($json);
Также хорошо отметить, что вы можете обращаться к объектам так же, как к массивам в PHP, поэтому в типичном сценарии вы даже не почувствуете разницу. Хороший момент, хотя!
Посмотрите результаты здесь (извините за взломать PHP-код в поле кода JS):
http://jsfiddle.net/newms87/h3b0a0ha/embedded/result/
РЕЗУЛЬТАТЫ: serialize()
и unserialize()
являются значительно более быстрыми в PHP 5.4 на массивах разного размера.
Я сделал тест script для данных реального мира для сравнения json_encode vs serialize и json_decode vs unserialize. Тест проводился на системе кэширования на веб-сайте электронной коммерции. Он просто берет данные уже в кеше и проверяет время для кодирования/декодирования (или сериализации/несериализации) всех данных, и я помещаю его в удобную для просмотра таблицу.
Я запустил это на сервере хостинга PHP 5.4.
Результаты были очень убедительными, что для этих больших и малых наборов данных сериализация и неэтериализация были явными победителями. В частности, для моего варианта использования json_decode и unserialize являются наиболее важными для системы кэширования. Unserialize был почти повсеместным победителем здесь. Это было обычно от 2 до 4 раз (иногда 6 или 7 раз) так же быстро, как json_decode.
Интересно отметить разницу в результатах от @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>
просто fyi - если вы хотите сериализовать свои данные на что-то легкое для чтения и понимания, как JSON, но с большим сжатием и более высокой производительностью, вы должны проверить messagepack.
Если суммировать то, что говорят здесь люди, json_decode/encode кажется быстрее, чем serialize/unserialize НО Если вы выберете var_dump, тип сериализованного объекта будет изменен. Если по какой-то причине вы хотите сохранить тип, перейдите к сериализации!
(попробуйте, например, stdClass vs array)
сериализации/десериализации:
Array cache:
array (size=2)
'a' => string '1' (length=1)
'b' => int 2
Object cache:
object(stdClass)[8]
public 'field1' => int 123
This cache:
object(Controller\Test)[8]
protected 'view' =>
json encode/decode
Array cache:
object(stdClass)[7]
public 'a' => string '1' (length=1)
public 'b' => int 2
Object cache:
object(stdClass)[8]
public 'field1' => int 123
This cache:
object(stdClass)[8]
Как вы видите, json_encode/decode преобразует все в stdClass, что не так хорошо, информация об объекте потеряна... Поэтому решайте на основе потребностей, особенно если это не только массивы...
Во-первых, я изменил script, чтобы выполнить еще один бенчмаркинг (а также сделать 1000 запусков вместо 1):
<?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);
$totalJsonTime = 0;
$totalSerializeTime = 0;
$totalJsonWins = 0;
for ($i = 0; $i < 1000; $i++) {
// Time json encoding
$start = microtime(true);
$json = json_encode($testArray);
$jsonTime = microtime(true) - $start;
$totalJsonTime += $jsonTime;
// Time serialization
$start = microtime(true);
$serial = serialize($testArray);
$serializeTime = microtime(true) - $start;
$totalSerializeTime += $serializeTime;
if ($jsonTime < $serializeTime) {
$totalJsonWins++;
}
}
$totalSerializeWins = 1000 - $totalJsonWins;
// Compare them
if ($totalJsonTime < $totalSerializeTime) {
printf("json_encode() (wins: $totalJsonWins) was roughly %01.2f%% faster than serialize()\n", ($totalSerializeTime / $totalJsonTime - 1) * 100);
} else {
printf("serialize() (wins: $totalSerializeWins) was roughly %01.2f%% faster than json_encode()\n", ($totalJsonTime / $totalSerializeTime - 1) * 100);
}
$totalJsonTime = 0;
$totalJson2Time = 0;
$totalSerializeTime = 0;
$totalJsonWins = 0;
for ($i = 0; $i < 1000; $i++) {
// Time json decoding
$start = microtime(true);
$orig = json_decode($json, true);
$jsonTime = microtime(true) - $start;
$totalJsonTime += $jsonTime;
$start = microtime(true);
$origObj = json_decode($json);
$jsonTime2 = microtime(true) - $start;
$totalJson2Time += $jsonTime2;
// Time serialization
$start = microtime(true);
$unserial = unserialize($serial);
$serializeTime = microtime(true) - $start;
$totalSerializeTime += $serializeTime;
if ($jsonTime < $serializeTime) {
$totalJsonWins++;
}
}
$totalSerializeWins = 1000 - $totalJsonWins;
// Compare them
if ($totalJsonTime < $totalSerializeTime) {
printf("json_decode() was roughly %01.2f%% faster than unserialize()\n", ($totalSerializeTime / $totalJsonTime - 1) * 100);
} else {
printf("unserialize() (wins: $totalSerializeWins) was roughly %01.2f%% faster than json_decode()\n", ($totalJsonTime / $totalSerializeTime - 1) * 100);
}
// Compare them
if ($totalJson2Time < $totalSerializeTime) {
printf("json_decode() was roughly %01.2f%% faster than unserialize()\n", ($totalSerializeTime / $totalJson2Time - 1) * 100);
} else {
printf("unserialize() (wins: $totalSerializeWins) was roughly %01.2f%% faster than array json_decode()\n", ($totalJson2Time / $totalSerializeTime - 1) * 100);
}
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 7:
PHP 7.0.14 (cli) (построено: 18 января 2017 19:13:23) (NTS) Copyright (c) 1997-2016 PHP Group Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies с Zend OPcache v7.0.14, Copyright (c) 1999-2016, Zend Technologies
И мои результаты:
serialize() (побед: 999) был примерно на 10,98% быстрее, чем json_encode() unserialize() (побед: 987) был примерно на 33,26% быстрее, чем json_decode() unserialize() (побед: 987) был примерно на 48,35% быстрее, чем массив json_decode()
Значит ясно, serialize/unserialize является самым быстрым методом, а json_encode/decode является наиболее переносимым.
Если вы рассматриваете сценарий, в котором вы читаете/записываете сериализованные данные 10x или чаще, чем вам нужно отправлять или получать из системы, отличной от PHP, вы STILL лучше использовать serialize/unserialize и иметь json_encode или json_decode до сериализации с точки зрения времени.
JSON лучше, если вы хотите сделать резервную копию данных и восстановить их на другом компьютере или через FTP.
Например, с сериализацией, если вы храните данные на сервере Windows, загрузите его через FTP и восстановите его на Linux, который больше не может работать из-за повторного кодирования charachter, поскольку сериализация сохраняет длину строк и в перекодировке Unicode > UTF-8, 1 байт-хакер может стать 2 байтами, что приведет к сбою алгоритма.
спасибо - для этого эталонного кода:
Мои результаты по массиву, который я использую для конфигурации, являются парой:
JSON закодирован в 0,0031511783599854 секунд
PHP сериализуется в 0,0037961006164551 секунд json_encode()
был примерно на 20,47% быстрее, чем serialize()
JSON закодирован в 0,0070841312408447 секунд
PHP сериализуется в 0,0035839080810547 секунд unserialize()
был примерно на 97,66% быстрее, чем json_encode()
Итак, проверьте его на свои собственные данные.