너의 개발은/PHP

PHP: 제네레이터 (Iterator)

제네레이터

제네레이터는 모던 PHP에서 등장한 흥미로운 기능으로, 내부적으로 Iterator 인터페이스를 구현한 빌트인 클래스입니다. 생긴건 함수처럼 생겼지만, 함수 내부에서 yield 라는 키워드를 사용하여 마치 Break Point 를 넣은 것처럼, 값을 외부에 던져주고 다시 내부로 돌아가서 진행할 수 있습니다.

 

사실 이야기만 들어서는 잘 모를 수 있는데, 코드를 보면 쉽게 알 수 있을 것입니다. 자바스크립트에도 제네레이터는 존재하지만, 그보다는 복잡하지 않습니다.

yield

제네레이터는 마치 함수처럼 생겼습니다. 그런데 사실 이렇게 만든 제네레이터를 호출해서 값을 반환받으면 흥미로운 객체를 얻을 수 있습니다. 바로 제네레이터 객체입니다. 즉, 함수의 모습을 하고 있지만, 실은 전혀 다른 개념이라는 것을 알 수 있죠.

function gen()
{
    yield 1;
    yield 2;
    yield 3;
}

$gen = gen();
var_dump($gen); // -> class Generator#0 {}

제네레이터 사용하기

제네레이터는 Iterator 인터페이스를 구현합니다. 따라서 수동으로 사용할 수 있습니다. 그런데 이터레이터 인터페이스를 구현한다는 것은 다른 말로 foreach 를 사용할 수 있다는 이야기이기도 합니다.

Iterator 인터페이스

var_dump($gen->current()); // -> 1

$gen->next();
var_dump($gen->current()); // -> 2

$gen->next();
var_dump($gen->current()); // -> 3

foreach

foreach (gen() as $number) {
    var_dump($number);
}

yield

yield 키워드의 사용법을 조금만 더 살펴보도록 하겠습니다.

yield from

제네레이터 내부에서 또 다른 제네레이터를 호출할 수 있습니다. 재귀로도 사용할 수 있죠!

function gen2()
{
    yield 1;
    yield from gen();
    yield 2;
}

// -> 11232
foreach (gen2() as $number) {
    var_dump($number);
}

yield $key => $value

function gen3()
{
    $messages = [
        'sayHello' => 'Hello, world'
    ];
    foreach ($messages as $key => $message) {
        yield $key => $message;
    }
}

// -> sayHello Hello, world
foreach (gen3() as $key => $message) {
    var_dump($key, $message);
}

$data = yield

제네레이터를 한 번 호출 후, 데이터를 넘겨서 처리해줄 수도 있습니다. 추가적으로 yield 키워드 뒤에 아무것도 주지 않으면 NULL 을 던져줍니다.

function gen4()
{
    $data = yield;
    yield $data;
}

$gen4 = gen4();

// -> NULL
var_dump($gen4->current());

var_dump($gen4->send('Hello, world'));
// -> Hello, world
var_dump($gen4->current());

제네레이터를 사용하면 좋은 점

제네레이터의 장점이 무엇인지 살펴보도록 하죠. 다른 개념들은 말로 이야기할 수 있지만, 제네레이터의 경우 직접 실행해보고 결과를 살펴보는 것이 좋습니다. 먼저 range() 함수를 통해 0, 100000 까지의 수를 담은 배열을 만들고 이를 반복문으로 돌려보겠습니다. 그런 다음, 메모리 사용량속도를 간단하게 체크합니다.

일괄 처리

range() 를 사용하면, 다소 큰 배열이 일괄적으로 생성되기때문에 메모리의 사용량이 많아집니다. 속도는 어떨까요? 상대적인 수치가 필요할테니 바로 다음에서 비교해보면 알 것입니다. 다시 말하면 제네레이터를 사용하면 일괄적으로 처리되지 않는다는 이야기가 됩니다.

$s = microtime(true);

foreach (range(0, 100000) as $number) {
}

// -> 0.0069749355316162, 6698152
var_dump(microtime(true) - $s, memory_get_peak_usage());

제네레이터

먼저 제네레이터 __range() 를 만들어보도록 하고, 이를 바탕으로 다시 측정해보도록 하겠습니다. 

function __range($start, $end, $step = 1)
{
    for ($i = $start; $i <= $end; $i += $step) {
        yield $i;
    }
}

$s = microtime(true);

foreach (__range(0, 100000) as $number) {
}

// -> 0.45167684555054, 446536
var_dump(microtime(true) - $s, memory_get_peak_usage());

제네레이터를 사용했더니 속도는 더 느리지만, 메모리 사용량이 줄어든 것을 볼 수 있습니다. 보통 작은 서버에는 대용량 메모리가 탑재되어 있지 않은 경우가 많으며 메모리는 컴퓨팅 자원 중에서 비싼 자원에 속합니다. 그래서 약간의 속도를 포기하고 메모리를 취하는 방법도 택할 수 있는 것입니다.

 

대표적으로 사용해볼 수 있는 예시로는 파일 처리가 있습니다. 큰 파일을 한꺼번에 읽어서 처리하게 되면 한순간에 많은 메모리를 소모하게 됩니다. 그렇게 되면 서버의 자원이 줄어들어 다른 프로세스가 제대로 동작할 수 없게 될 수도 있습니다. 메모리 수치를 비교해보면 실행시간을 희생해서 감수할만큼의 가치가 충분히 있습니다.

Iterator 인터페이스

이터레이터 인터페이스를 직접 구현하는 것도 해볼 수 있습니다. 그렇게 어렵지 않으니 코드를 그대로 적어서 해보시기 바랍니다. 제네레이터랑 똑같이 사용할 수 있습니다.

IntegerIterator

위에서 만든 __range() 제네레이터를 직접 인터페이스를 구현하여 나타낸것입니다. 즉, 제네레이터는 이너레이터 인터페이스 구현체를 간단하게 쓸 수 있게해놓은 축약 버전이라고 볼 수 있습니다. 정리하자면 그렇게 되는군요!

class IntegerIterator implements Iterator
{
    private $i;

    public function __construct($start, $end, $step = 1)
    {
        $this->start = $i = $start;
        $this->end = $end;
        $this->step = $step;
    }

    public function rewind()
    {
        $this->i = 0;
    }

    public function valid()
    {
        return $this->i <= $this->end;
    }

    public function current()
    {
        return $this->i;
    }

    public function key()
    {
        return $this->i;
    }

    public function next()
    {
        $this->i += $this->step;
    }
}

foreach (new IntegerIterator(0, 100) as $number) {
}

 

 

PHP 7+ 프로그래밍: 리부트 - 인프런

기초 문법부터 내장 함수, 웹 보안, 게시판 만들기까지 PHP 언어를 시작하는 분들을 위해 바이블이 될 수 있게 만들어보고자 하는 마음으로 이번 강좌를 만들어보았습니다. 입문 웹 개발 프로그��

www.inflearn.com

 

PHP 7+ 프로그래밍: 객체지향 - 인프런

PHP 객체지향, 내장 클래스, PSR, Composer, MVC(Model, View, Controller)까지 모던 PHP를 익히기 위한 근간을 이야기합니다. 초급 프로그래밍 언어 알고리즘 PHP 객체지향 알고리즘 온라인 강의 모던 PHP 프로��

www.inflearn.com

'너의 개발은 > PHP' 카테고리의 다른 글

PHP: 객체 비교와 복사  (0) 2020.04.25
PHP: 참조 (WeakReference)  (0) 2020.04.25
PHP: 제네레이터 (Iterator)  (0) 2020.04.24
PHP: 에러와 예외 (try, catch, finally)  (1) 2020.04.23
PHP: 네임스페이스  (0) 2020.04.22
PHP: 매직 메서드  (1) 2020.04.22