PHP FFI 實現list

aa楊發表於2022-05-12

本文通過PHP FFI特性初步實現了list連結串列

union list
{
    int val;
    char *valStr;
};

struct ArrayStringList
{
    int length;      //當前長度
    int capacity; //容量
    char *data;      //陣列指標
};
<?php
// php 字串轉C char指標
function stringToCharPtr(string $str)
{
    $strChar = str_split($str);

    $c = FFI::new('char[' . count($strChar) . ']', false);
    foreach ($strChar as $i => $char) {
        $c[$i] = $char;
    }
    return FFI::cast(FFI::type('char *'), $c);
}


class FFIHelper
{
    private static $ffi;
    public static function create()
    {
        if (empty(self::$ffi)) {
            self::$ffi = \FFI::load("./test.h");
        }
        return self::$ffi;
    }
}

class StringArray
{
    private $char;
    private $ArrayList;

    public function __construct(int $capacity = 0)
    {
        if ($capacity > 0) {
            $this->create($capacity);
        }
    }

    /**
     * 建立list
     */
    public function create(int $capacity)
    {
        if (!is_numeric($capacity) || $capacity <= 0) {
            throw new \Exception("list長度不可以為0");
        }
        $this->char                = \FFI::new ('char*[' . ($capacity) . ']', false, true);
        $this->ArrayList           = FFIHelper::create()->new('struct ArrayStringList');
        $this->ArrayList->capacity = $capacity;
    }

    public function append($string)
    {
        $postion = $this->ArrayList->length;
        if ($postion >= $this->ArrayList->capacity) {
            $this->grow($this->ArrayList->capacity * 2);
        }
        $this->char[$postion] = stringToCharPtr($string . "\0");
        if ($postion == 0) {
            $this->ArrayList->data = $this->char[0];
        }
        $this->ArrayList->length++;
    }

    public function get($postion)
    {
        return $this->ArrayList->data;
    }

    public function delete($postion)
    {
        if ($postion < 0) {
            throw new \Exception("刪除位置不可以小於0");
        }
        if ($postion > $this->ArrayList->length) {
            throw new \Exception("刪除位置大於list長度");
        }
        $node = $this->ArrayList->data + $postion;
        for ($i = $postion + 1; $i < $this->ArrayList->length; $i++) {

        }
    }

    public function length()
    {
        return $this->ArrayList->length;
    }

    /**
     * 增加陣列長度
     */
    public function grow($size)
    {
        if ($size < $this->ArrayList->capacity) {
            throw new \Exception("無需增加list容量");
        }
        $oldData = $this->ArrayList->data;
        $newData = \FFI::new ('char*[' . ($size) . ']', false, true);
        \FFI::memcpy($newData, $this->char, \FFI::sizeof($oldData) * $this->ArrayList->length);
        $this->ArrayList->data     = $newData[0];
        $this->char                = $newData;
        $this->ArrayList->capacity = $size;
        \FFI::free($oldData);
    }

    public function getList()
    {
        return $this->ArrayList;
    }

    public function __destruct()
    {

    }
}
$star_memory = memory_get_usage();
$start       = microtime(true);

$list = new StringArray(1000000);
var_dump($list->append("aaas你好"));
var_dump($list->append("aaas你好"));
var_dump($list->append("aaas你好"));
$i    = 0;
$data = [];
while (true) {
    $list->append("aaas你好");
    //$data[] = "aaas你好" . $i;
    $i++;
    if ($i > 1000000) {
        break;
    }
}
var_dump(FFI::string($list->get(0)));
$end_memory = memory_get_usage();
$elapsed    = microtime(true) - $start;

echo "That took $elapsed seconds.\n";

var_dump((($end_memory - $star_memory) / 1024 / 1024) . "M");

由於PHP底層字串做了處理,相同字串會只存一次,通過計數器的方式來表示引用的次數,而本文中實現的字串並未進行認為處理。因而,每次都會重新建立新的字串。

通過文中程式碼測試,發現即使為對字串進行處理,記憶體佔用情況也會至少優化3倍以上,當然目前美中不足的是,字串轉為char指標耗時比較久,扔需要優化。

相關文章