коробка / носик - массовое использование памяти при использовании шестнадцатеричного цвета по сравнению с предопределенными цветами - PullRequest
2 голосов
/ 05 февраля 2020

Я использую библиотеку Box / Spout , и кажется, что использование StyleBuilder с пользовательским шестнадцатеричным цветом (например, 0000FF для синего) использует тонну памяти по сравнению с использованием предопределенных цветов, таких как Color :: СИНИЙ. Почему это так?

Соответствующий фрагмент:

//LOW MEMORY
$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor(Color::BLUE)->build());

//HIGH MEMORY
$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor($colorHex)->build());

Вывод:

setFontColor (Color :: BLUE) : Peak memory usage: 1666 KB

setFontColor ($ colorHex): Peak memory usage: 189436 KB

Полный код:

(для демонстрации я загружаю небольшой Изображение 250x150 для обеспечения нескольких значений цвета)

<?php

    require_once 'Spout/Autoloader/autoload.php';
    use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
    use Box\Spout\Common\Entity\Style\Color;
    use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;

    //load an image
    $img = imagecreatefrompng('input/test250x150.png');

    $writer = WriterEntityFactory::createXLSXWriter();
    $writer->openToFile('output/MyExcel.xlsx');

    //height of the image
    for($y=0; $y<150; $y++) {

        //create or reset array to hold this row's cells
        $row = [];

        //width of the image
        for($x=0; $x<250; $x++) {

            //gets the pixel color
            $index = imagecolorat($img, $x, $y);
            $colorRGBArr = imagecolorsforindex($img, $index);
            $colorHex = sprintf("%02x%02x%02x", $colorRGBArr['red'], $colorRGBArr['green'], $colorRGBArr['blue']);

            //LOW MEMORY
            //$row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor(Color::BLUE)->build());
            //HIGH MEMORY
            $row[] = WriterEntityFactory::createCell('test', (new StyleBuilder())->setFontColor($colorHex)->build());

        }
        $writer->addRow(WriterEntityFactory::createRow($row));
    }

    $writer->close();

    echo 'Peak memory usage: '.round(memory_get_peak_usage() / 1024).' KB';
?>

1 Ответ

4 голосов
/ 08 февраля 2020

tl; dr

Хотя Spout можно улучшить, Excel не предназначен для большого количества стилей , так что это на самом деле не недостаток библиотеки ( возможно, вы захотите отменить вопрос )

История

Хорошо, здесь есть несколько моментов, которые я использовал для тестирования ... код, который я использовал для тестирования, находится внизу мой пост - ключевые цветовые функции: jonEg и jonEgQuant (переменная $by внутри, которую можно настроить)

  • Стилизация в Excel немного похожа на HTML + CSS. Стили определяются вначале в файле (в блоке заголовка), а затем на листах указывается имя (класс). По сути, это говорит о том, что формат хранения Excel не был разработан для множества стилей, а только несколько, которые повторно используются во многих ячейках

  • Spout скрывает сложность определения стиля от пользователя - если стиль уже определен (ie. , точный стиль уже существует ), то он использует это определение, в противном случае определяется новый стиль

  • Spout создает индекс всех стилей путем сериализации объекта стиля и сохранения его в массиве. Примерно 1749 байт на стиль. Этот индекс жует память (в дополнение к фактическим объектам стиля, которые есть в памяти для генерации файла) - это можно улучшить в Spout

  • С этим кодом я гарантирую, что каждая ячейка имеет свой стиль (используя положение строки / столбца для определения красного и зеленого компонентов цвета) - , что является более экстремальным, чем ваше изображение-цвет- выбор, но, возможно, не намного (я не могу сказать без образца изображения). Есть 16M цветов, доступных с триплетом RGB ... но наши глаза не всегда могут обнаружить разницу в несколько пунктов. Например, в красном градиенте будет ясно, что 255,254,253 ... 128 выглядит гладким, но один блок из 255,254,253 случайно распределенных будет, вероятно, выглядеть как один цвет. Для всего, кроме основного цвета, эта дисперсия теперь применяется в трех измерениях (r, g и b). Формат JPEG использует это (сочетание сжатия и шума при восстановлении) - поэтому при выборе цвета, который может выглядеть как единообразный блок, каждый раз будут возвращаться немного разные значения

  • Когда вы запускаете мой код с jonEg, вы получаете (100 * 150 + 1) 15001 стилей, что занимает (15001 * 1749/1024/1024) ~ 25 МБ памяти для индекса только в Spout. Опять же, этот индекс необходим для предотвращения того, чтобы каждая ячейка в Excel имела свой собственный стиль (но, конечно, в этом надуманном примере я убедился, что он бессмысленен - ​​у каждой ячейки есть свой собственный стиль) - здесь используется ~ 100 МБ памяти

  • Когда вы запускаете мой код с jonEgQuant (оставляя $by=16;), требуется только 71 стиль (но это довольно экстремальное округление, позволяющее всего 4096 цветов ), - при этом используется ~ 2 МБ памяти

  • При установке $by=4 (внутри ColorBuilder::jonEgQuant) - теперь у вас до четверти миллиона цветов Требуется 989 стилей с использованием ~ 7 МБ памяти (точнее, при открытии в Excel это похоже на jonEg)

  • Spout включает метод преобразования десятичных значений RGB в строку Color::rgb($r,$g,$b) - но это не меняет результат (spoutDoc метод)

  • Стиль имеет гораздо больше измерений, чем этот - Я использовал цвет фона, но есть передний план (ваш пример), подчеркивание, жирный, я tali c, начертание шрифта, размер шрифта - все это умножает количество стилей (сокращение цветового пространства здесь решает вашу задокументированную проблему, но может быть отменено путем изменения других компонентов стиля)

Take Aways

  • Проблема не в использовании шестнадцатеричных кодов, значений RGB или констант, а в количестве используемых вами стилей

  • Сократите количество используемых стилей - Excel не ожидает большого количества, Spout также не оптимизировано для этого. Сжатие цвета (округляя значения, как с jonEgQuant) - это одно направление

  • Улучшение механизма индекса стиля Spout, но обратите внимание, что индекс использует только часть памяти - он не маленький, но это не единственный участник - каждый объект стиля необходим для генерации результата. Удаление этого кэша стилей (который для этого надуманного примера не помогает) увеличивает использование памяти до 40 МБ (и создает тот же файл Excel). Экономия на 60% - это не что иное, но больше стилей позволяет увеличить использование памяти, увеличить файлы Excel и медленнее открывать / отображать эти файлы

Тестирование заметок

  • Я пропустил чтение изображения / сбор цветов во время тестирования, поскольку это может увеличить использование памяти, но не суть проблемы

  • С $colCount=250; (из вашего примера ) вы превышаете предел памяти по умолчанию 128 МБ в PHP, лучше установить его $colCount=100;, и вы можете запустить все тесты, не изменяя этот параметр

  • Я переключился на настройку background-color легче увидеть разницу при открытии в Excel

  • Для каждого генератора цвета (jonEg, spoutDoc, fixedEg, jonEgQuant) генерируется другой XLSX ( помогает увидеть размер файла и различия при рендеринге)

  • Размеры файлов соответствуют сложности Excel - jonEg - это файл размером 179 КБ (и Excel пытается открыть), тогда как jonEgQuant - 43KB

Код

<?php

require_once 'Spout/Autoloader/autoload.php';
use Box\Spout\Writer\Common\Creator\WriterEntityFactory;
use Box\Spout\Common\Entity\Style\Color;
use Box\Spout\Writer\Common\Creator\Style\StyleBuilder;

// -- -- Set this to one of the method names on ColorBuilder (that isn't a helper)
$choice='jonEg';
// -- -- 

class ColorBuilder {
    static $defaultBlue=255;
    static function jonEg($x,$y) {return sprintf("%02x%02x%02x", $x, $y, static::$defaultBlue);}
    static function spoutDoc($x,$y) {return Color::rgb($x, $y, static::$defaultBlue);}
    static function fixedEg($x,$y) {return Color::BLUE;}
    static function jonEgQuant($x,$y) {$by=16;return sprintf("%02x%02x%02x", static::_quantize($x,$by),static::_quantize($y,$by), static::_quantize(static::$defaultBlue,$by));}

    //Helpers - don't use these for choice
    static function validate(string $name):bool {
        if ($name==null) return false;//Null or empty
        if (substr($name,0,1)=='_') return false;//Private by convention
        if ($name==='validate') return false;//Not the function you seek
        return method_exists('ColorBuilder',$name);
    }
    private static function _quantize(int $i,int $by=16):int {return round($i/$by)*$by;}
}

function createRow($y,$color) {
    $colCount=100;
    $row = [];

    for($x=0; $x<$colCount; $x++) {
        $row[] = WriterEntityFactory::createCell('*', (new StyleBuilder())->setBackgroundColor(ColorBuilder::$color($x,$y))->build());
    }
    return $row;
}

function buildSheet($name) {
    if (!ColorBuilder::validate($name)) {throw new Error('Invalid color provider');}

    $writer = WriterEntityFactory::createXLSXWriter();
    $writer->openToFile('output/'.$name.'.xlsx');

    for($y=0; $y<150; $y++) {
        $writer->addRow(WriterEntityFactory::createRow(createRow($y,$name)));
    }

    $writer->close();
}

buildSheet($choice);
echo 'Peak memory usage: '.round(memory_get_peak_usage() / 1024).' KB';

* 1 140 * Технология: PHP 7.4.2 CLI, Spout: 3.1.0, Win: 7 x64 (I know), Coffee: Venti Dark

...