Just before ISUCON10! An example of speeding up processing

Sep 4, 2020 PHP isucon high speed

はじめに

高速化の手法の一例を共有する

高速化前のコード

<?php
class Data {
    public function get_data_old(){
        $pdo = new PDO('mysql:host=localhost;dbname=array_speed_up;','root');
        $query = 'SELECT * FROM `data`';
        $result = $pdo->query($query)->fetchAll(PDO::FETCH_ASSOC);
        return $result;
    }
}

function old_code(){
    $data = new Data();
    $records = $data->get_data_old();
    $tmp_array = array();
    foreach($records as $record){
        array_push($tmp_array, $record['id'].','.'"'.$record['value'].'"');
    }
    //print_r($tmp_array);
    return($tmp_array);
}

echo("--- old ---\n");
$time_start = microtime(true);
$memory_first = memory_get_usage() / (1024 * 1024);
old_code();
$usage_memory = memory_get_usage() / (1024 * 1024);
$memory = $usage_memory - $memory_first;
$memory = round($memory, 4);
$time = microtime(true) - $time_start;
$time = round($time, 4);
echo($memory." MB\n");
echo($time." 秒\n");

DBは以下の構成

  CREATE TABLE `data` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `value` varchar(255) NOT NULL DEFAULT '',
    PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

DBからレコードを全て取得し id,"value" の形に整形して画面に出力する、というような処理

問題点

まぁ別にレコードが少なければ問題なく動くが全件取得して表示するためレコードが増えれば増えるほど重い またレコード数分のループを回しているのも重い $tmp_arrayに都度整形した結果を格納していくスタイルなので$records$tmp_arrayで2倍の配列を確保するのも重い

高速化後のコード

<?php
class Data {
    public function get_data_refactor(){
        $pdo = new PDO('mysql:host=localhost;dbname=array_speed_up;','root');
        $query = 'SELECT CONCAT(CONCAT(CONCAT(CONCAT(`id`, ","), """"), `value`), """") FROM `data`';
        $result = $pdo->query($query)->fetchAll(PDO::FETCH_COLUMN);
        return $result;
    }
}

function refactor_code(){
    $data = new Data();
    $result = $data->get_data_refactor();
    //print_r($result);
    return($result);
}

echo("--- refactor ---\n");
$time_start = microtime(true);
$memory_first = memory_get_usage() / (1024 * 1024);
refactor_code();
$usage_memory = memory_get_usage() / (1024 * 1024);
$memory = $usage_memory - $memory_first;
$memory = round($memory, 4);
$time = microtime(true) - $time_start;
$time = round($time, 4);
echo($memory." MB\n");
echo($time." 秒\n");

整形処理をDBから取得するタイミングで既に終わらせているため、データ整形のループが不要 $tmp_arrayのような整形後のデータを格納する配列も不要

性能比較

レコード数 before after
1 0.0177 秒 0.0014 秒
1000 0.0295 秒 0.0074 秒
10000 0.1153 秒 0.0685 秒
100000 1.1397 秒 0.5736 秒

実行時間は大体倍くらいに速くなった。 メモリに関しては0.0002MB0.0001MBに減った。

まとめ

そのループ本当に必要か? その配列本当に必要か? そのテーブルはMAXどの程度レコードが入る想定なのか? 考えうる最善を尽くしていけると良いですね。