php1$a = ['q_lazzarus' => '킹왕짱'];2echo $a['q_lazzarus'];
다시 기초로 돌아가자면, array 는 동일한 자료구조의 반복 입니다.
array data structure
메모리 단위에서 생각해보면 동일한 크기의 방이 주루룩 있는 구조이죠.
!!
그렇다면, string 하나에 integer 몰빵해서 넣으면 되자너?
profit!!
5개의 원소가 있는 배열이 있다고 가정하고 데이터를 읽는 간단한 함수를 만들어 보겠습니다.
php1function array_get($i) {2 $data = '12345';3 return substr($data, $i, 1);4}56echo array_get(3);
Plain Text13
Data Type | Size (in bytes) | Range |
---|---|---|
short int | 2 | -32,768 to 32,767 |
unsigned short int | 2 | 0 to 65,535 |
unsigned int | 4 | 0 to 4,294,967,295 |
int | 4 | -2,147,483,648 to 2,147,483,647 |
long int | 4 | -2,147,483,648 to 2,147,483,647 |
unsigned long int | 4 | 0 to 4,294,967,295 |
long long int | 8 | -(2^63) to (2^63)-1 |
unsigned long long int | 8 | 0 to 18,446,744,073,709,551,615 |
singed char | 1 | -128 to 127 |
unsigned char | 1 | 0 to 255 |
php1$data = str_repeat(pack('I', null), 5);23function array_get($i) {4 global $data;5 $binary = substr($data, $i * 4, 4);6 $unpack = unpack('I', $binary);7 return $unpack[1];8}910function array_set($i, $value) {11 global $data;12 $binary = pack('I', $value);13 $data = substr_replace($data, $binary, $i * 4, 4);14}1516array_set(3, 65535);17echo array_get(3);
Plain Text165535
php1echo array_get(2);
Plain Text10
아니 어떻게 된 일이죠?? 분명히 공간만 만들어주기 위해서 null 을 넣어주었는데?
생각해보면 데이터를 넣을때 한칸씩 미루면 되지 않을까요?
php1$data = str_repeat(pack('I', 0), 5);23function array_get($i) {4 global $data;5 $binary = substr($data, $i * 4, 4);6 $unpack = unpack('I', $binary);7 $result = $unpack[1];8 if (0 === $result) {9 return null;10 }11 return $result - 1;12}1314function array_set($i, $value) {15 global $data;16 if (null !== $value) {17 $value = $value + 1;18 }19 $binary = pack('I', $value);20 $data = substr_replace($data, $binary, $i * 4, 4);21}2223var_dump(array_get(3));
Plain Text1NULL
php1ArrayAccess {2 /* Methods */3 abstract public offsetExists ( mixed $offset ) : bool4 abstract public offsetGet ( mixed $offset ) : mixed5 abstract public offsetSet ( mixed $offset , mixed $value ) : void6 abstract public offsetUnset ( mixed $offset ) : void7}
php1$obj = new obj;2$obj[] = 'hello';3$obj[] = 'world';45echo $obj[0] . ' ' . $obj[1];
Plain Text1hello world
php1class FixedUnsignedIntegerArray implements Iterator, ArrayAccess, Countable2{3 const BINARY_FORMAT = 'I';4 private $position = 0;5 private $length = 0;6 private $binaryLength = 0;7 private $data = null;8 public function __construct($length)910 {11 $null = $this->convertBinary(null);12 $this->position = 0;13 $this->length = $length;14 // because binary length is machine dependency15 $this->binaryLength = strlen($null);16 $this->data = str_repeat($null, $length);17 }1819 private function getEntry($position)20 {21 $position = $position * $this->binaryLength;22 $unpack = unpack(self::BINARY_FORMAT, substr($this->data, $position, $this->binaryLength));23 $result = false !== $unpack ? $unpack[1] : null;24 if (0 === $result) {25 return null;26 } else {27 return $result - 1;28 }29 }3031 private function setEntry($position, $value)32 {33 $position = $position * $this->binaryLength;34 $this->data = substr_replace($this->data, $this->convertBinary($value), $position, $this->binaryLength);35 }3637 private function convertBinary($value)38 {39 if (null !== $value) {40 $value = $value + 1;41 }42 return pack(self::BINARY_FORMAT, $value);43 }4445 public function rewind()46 {47 $this->position = 0;48 }4950 public function current()51 {52 return $this->getEntry($this->position);53 }5455 public function key()56 {57 return $this->position;58 }5960 public function next()61 {62 $this->position = $this->position + 1;63 }6465 public function valid()66 {67 return $this->offsetExists($this->position);68 }6970 public function offsetSet($offset, $value)71 {72 if (is_integer($offset) && $offset < $this->length && $offset >= 0) {73 $this->setEntry($offset, $value);74 } else {75 throw new InvalidArgumentException('overflow array offset');76 }77 }7879 public function offsetExists($offset)80 {81 return ($offset < $this->length && $offset >= 0);82 }8384 public function offsetUnset($offset)85 {86 $this->setEntry($offset, null);87 }8889 public function offsetGet($offset)90 {91 return $this->getEntry($offset);92 }9394 public function count()95 {96 return $this->length;97 }98}
unsigned integer (machine dependent size and byte order)
이제 구현했으니, 성능을 테스트 해보겠습니다.
테스트는 다음과 같이 진행하였습니다.
php1define('ARRAY_LENGTH', 10000);23$start_memory = memory_get_usage();4$vanilla = [];5for ($i = 0; $i < ARRAY_LENGTH; $i++) {6 $vanilla[] = mt_rand(0, 255);7}89$end_memory = memory_get_usage() - $start_memory;10echo "legacy array memory usage : {$end_memory}\n";1112$start = microtime(true);13$result = 0;14for ($i = 0; $i < ARRAY_LENGTH; $i++) {15 $result += $vanilla[$i];16}1718$time_elapsed_secs = microtime(true) - $start;19echo "for iterator legacy array : {$time_elapsed_secs}\n";2021$start_memory = memory_get_usage();22$entries = new \Monoless\Arrays\FixedUnsignedIntegerArray(ARRAY_LENGTH);23for ($i = 0; $i < ARRAY_LENGTH; $i++) {24 $entries[$i] = mt_rand(0, 255);25}2627$end_memory = memory_get_usage() - $start_memory;28echo "fixed unsigned integer array memory usage : {$end_memory}\n";2930$time_total = 0;31$interval = 100;32for ($j = 0; $j < $interval; $j++) {33 $start = microtime(true);3435 $result = 0;36 for ($i = 0; $i < ARRAY_LENGTH; $i++) {37 $result += $entries[$i];38 }3940 $time_elapsed_secs = microtime(true) - $start;41 $time_total += $time_elapsed_secs;42}4344$time_average = $time_total / $interval;45echo "iterator speed average : {$time_average}\n";
Plain Text1legacy array memory usage : 5284402for iterator legacy array : 0.000358104705810553fixed unsigned integer array memory usage : 410724iterator speed average : 0.0068418407440186
메모리 사용량은 약 90% 개선이 되었으나 속도는 기존 배열을 이길 수가 없었네요...
오늘도 망했어요...
혹시나 싶어서 php://memory 에 fwrite / fseek 를 구현해봤는데 더 느렸습니다...
Plain Text1for iterator legacy array : 0.00130891799926762string speed average : 0.00842791318893433php://memory speed average : 0.0098107981681824