PHpullh

PHP · 심층 가이드

PHP 성능 완전 정리

hrtime으로 실제 비용을 재고 OPcache와 preload로 부팅 비용을 줄이며 generator로 메모리를 아끼는, 9가지 PHP 성능 측정·개선 패턴을 다룹니다.

주제 9개 · 예제 코드 포함 · 최종 수정 2026-08-30 · 작성 pullh 편집팀

PHP 성능 이야기는 추측으로 시작하면 거의 틀립니다. 요청마다 스크립트를 다시 컴파일하는 구조 때문에 실행 시간의 상당 부분이 코드 본문이 아니라 부팅과 I/O에 쓰이고, 그래서 반복문 안쪽을 아무리 다듬어도 체감이 없는 경우가 흔합니다. 반대로 옵코드 캐시가 꺼져 있거나 대용량 배열을 통째로 메모리에 올리는 코드 한 줄이 전체를 좌우하기도 합니다. 측정 지점을 먼저 정하고 그다음에 손을 대는 순서가 이 가이드의 뼈대입니다.

앞쪽 예제는 진단, 뒤쪽 예제는 처방에 가깝습니다. hrtime 벤치마크로 나노초 단위의 기준선을 만들고 OPcache 상태 점검으로 운영 환경이 애초에 캐시를 쓰고 있는지 확인한 다음, 병목이 메모리 쪽이면 generator 로 메모리 절약PDO fetch 제너레이터로 적재를 지연 순회로 바꿉니다. 파일 입출력이 문제라면 대용량 파일 스트리밍이 같은 아이디어를 디스크에 적용한 형태이고, preload 스크립트 형태는 자주 쓰는 클래스를 시작 시점에 올려 두는 마지막 손질입니다.

마이크로 벤치마크에서 흔히 어긋나는 부분이 있습니다. hrtime은 단조 증가 시계라 시스템 시간이 조정돼도 영향을 받지 않지만, microtime은 그렇지 않아 짧은 구간 측정에서 음수 차이가 나오기도 합니다. generator는 한 번 소진하면 되감을 수 없으므로 같은 결과를 두 번 순회해야 한다면 그 자리에서 배열로 물질화해야 하고, 이때 메모리 이득은 사라집니다. preload는 프로세스를 재시작해야 갱신되므로 배포 절차에 재시작을 넣지 않으면 옛 코드가 계속 살아 있습니다.

01hrtime 벤치마크

가벼운 코드 조각을 나노초 단위로 측정하는 hrtime 예제입니다.

PHP code

<?php
$start = hrtime(true);
array_sum(range(1, 10000));
$elapsedNs = hrtime(true) - $start;

echo $elapsedNs;
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

02OPcache 상태 점검

운영 환경에서 OPcache 활성 여부를 먼저 확인하는 성능 진단 예제입니다.

PHP code

<?php
if (function_exists('opcache_get_status')) {
    $status = opcache_get_status(false);
    var_dump($status !== false);
}
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

03generator 로 메모리 절약

큰 목록을 배열 대신 generator로 순회해 메모리를 아끼는 예제입니다.

PHP code

<?php
function huge(int $count): Generator {
    for ($i = 0; $i < $count; $i++) {
        yield $i;
    }
}

echo iterator_count(huge(1000));
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

04정적 캐시 메모이제이션

반복 조회되는 작은 정적 데이터를 함수 내부 캐시에 넣는 예제입니다.

PHP code

<?php
function config(string $key): string {
    static $cache = ['env' => 'local', 'app' => 'pullh'];
    return $cache[$key];
}

echo config('app');
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

05foreach 와 array_map 비교용 골격

배열 처리 스타일별 비용을 실제로 재보는 벤치마크 골격 예제입니다.

PHP code

<?php
$numbers = range(1, 1000);

$start = hrtime(true);
$result = [];
foreach ($numbers as $number) {
    $result[] = $number * 2;
}
$foreachNs = hrtime(true) - $start;

$start = hrtime(true);
$mapped = array_map(fn (int $n): int => $n * 2, $numbers);
$mapNs = hrtime(true) - $start;

echo $foreachNs . ' / ' . $mapNs;
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

06대용량 파일 스트리밍

대용량 파일은 한 번에 메모리에 올리기보다 스트리밍 처리하는 편이 안정적임을 보여주는 예제입니다.

PHP code

<?php
$stream = fopen('php://temp', 'r+');
for ($i = 0; $i < 3; $i++) {
    fwrite($stream, "row-{$i}
");
}
rewind($stream);

while (($line = fgets($stream)) !== false) {
    echo trim($line) . PHP_EOL;
}
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

07PDO fetch 제너레이터

DB 결과를 한 번에 모두 가져오지 않고 지연 순회하는 예제입니다.

PHP code

<?php
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)');
$pdo->exec("INSERT INTO items (name) VALUES ('php'), ('go')");

function rows(PDO $pdo): Generator {
    $stmt = $pdo->query('SELECT name FROM items');
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
        yield $row;
    }
}

foreach (rows($pdo) as $row) {
    echo $row['name'] . PHP_EOL;
}
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

08preload 스크립트 형태

OPcache preload 파일이 어떤 구조로 생기는지 감을 잡는 예제입니다.

PHP code

<?php
// preload.php
require __DIR__ . '/vendor/autoload.php';
class_exists(App\Kernel::class);
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

09문자열 버퍼링

반복적인 문자열 연결 대신 버퍼 배열 후 implode로 합치는 예제입니다.

PHP code

<?php
$start = hrtime(true);
$buffer = [];
for ($i = 0; $i < 100; $i++) {
    $buffer[] = "row-{$i}";
}
$text = implode("
", $buffer);
$elapsed = hrtime(true) - $start;

echo strlen($text) . ' / ' . $elapsed;
알아두면 좋은 점

성능 예제는 추측보다 측정을 우선해야 하므로 작은 벤치마크를 같이 돌려 보세요.

자주 하는 실수

측정 없이 미세 최적화부터 시작하면 가독성만 잃고 실제 성능 이득은 거의 없을 수 있습니다.

정리하며

  • 최적화 전에 OPcache 활성 여부부터 확인하세요. 설정 하나가 코드 수십 줄보다 큰 차이를 냅니다.
  • 짧은 구간 측정에는 microtime 대신 hrtime을 쓰고, 반복 횟수를 충분히 늘려 노이즈를 눌러야 합니다.
  • generator는 한 번만 순회할 수 있습니다. 재사용이 필요하면 메모리 이득을 포기하고 배열로 담으세요.
  • 대용량 결과 집합은 fetchAll 대신 커서 순회로 바꾸면 피크 메모리가 데이터 크기와 무관해집니다.

더 깊이 들어가고 싶다면 PHP 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.