C# Image.FromFile - Массовая операция?

Чтобы поддерживать «высокую» производительность, при использовании нашего приложения мы используем (предварительно загруженные) списки изображений для всех представлений списков.

Просмотр списка biggeset может содержать список изображений из примерно 9000 изображений.

Итак, чтобы создать список изображений, мы использовали addRange(), который позволяет завершить работу практически в кратчайшие сроки.

Однако «загрузка» образов из файловой системы в массив image[] во время запуска занимает еще 4-5 секунд. (~ 80 МБ, на SSD)

Интересно, есть ли лучший способ заполнить «большие» списки изображений из (локальной) файловой системы?

В настоящее время мы используем:

System.IO.DirectoryInfo di2 = new System.IO.DirectoryInfo(Config.ProductImageLocalPath);
System.IO.FileInfo[] files2 = di.EnumerateFiles("*.jpg", SearchOption.AllDirectories).ToArray();

int idx = 1; //0 is reservevd for default image, which is already added.
foreach (FileInfo fi in files2)
{
    Resources.ImageIndex.Add(long.Parse(Path.GetFileNameWithoutExtension(fi.Name)), idx++);
}

Image[] images = Array.ConvertAll(files2, file => Image.FromFile(file.FullName));

Resources.ProductImageList_256.Images.AddRange(images);

ImageIndex - это просто dictionary<Long, int>, который позволяет отображать фактический id (полученный из имени файла) в индекс на основе 0 в соответствующей карте изображения и не требует времени для создания.

Очевидно, Image[] images = Array.ConvertAll(files2, file => Image.FromFile(file.FullName)); является узким местом, поскольку это равно 9000 обращений к Image.fromFile (есть ли еще какой-нибудь оптовый вариант?).

Есть здесь «лучшие» идеи?

Я понимаю, что вы хотите, чтобы этот список изображений был готов, но можете ли вы разделить работу и реализовать методы, используемые для Интернета, игр и карт Google. По сути, вы хотите загрузить подмножество того, что будет сразу видно, и загрузить остальное по запросу, задаче или фоновому исполнителю. Сначала вы можете начать с создания List <Image>, чтобы вы могли добавлять, и пока вы не удаляете просто добавление, вам не нужно беспокоиться о параллелизме.

Felipe Ramos 08.10.2018 20:47

@FelipeRamos загрузка по запросу - проблема со списками. Если вы получите набор результатов из некоторых элементов и начнете загружать отдельные изображения «по запросу», это замедлит весь рендеринг.

dognose 08.10.2018 21:26

Все изображения имеют размер, необходимый для ListView? Если изображения больше, чем требуется для ListView, вы можете создать подмножество эскизов. Я бы также rootDir.EnumerateFiles("*.jpg", SearchOption.AllDirectories).Select(s=> s.FullName); просто выделил имена, так как это то, что вам нужно, чтобы не хранить коллекцию FileInfo в памяти. Не знаю, какую версию фреймворка вы используете, но вы можете попробовать Parallel.ForEeach, прежде чем возиться с Threading.

Felipe Ramos 08.10.2018 21:44

@FelipeRamos да, "Parallel.ForEeach" было бы отличной идеей :-) (Ну, я не возился с потоками, потоки - мои любимые компаньоны целую вечность) - Но я действительно попробую, если " Parallel.ForEeach "может быть более эффективным, чем балансирование работы старой школы.

dognose 08.10.2018 21:48

@FelipeRamos Смотрите мое обновление - оно немного быстрее, но важнее: его легче читать, понимать и поддерживать :)

dognose 08.10.2018 22:05

Хорошая сделка @dognose Я рад, что вы видите некоторые успехи - сейчас он выглядит оптимизированным. Если я найду что-нибудь еще, я обязательно обновлю сообщение.

Felipe Ramos 08.10.2018 22:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
126
1

Ответы 1

Итак, я написал функцию, которая будет выполнять работу, распределенную по нескольким потокам, - а затем обнаружил «ошибку» в моем коде. (Однако использование многопоточности очень помогает, поэтому image.fromFile явно ограничен процессором, а не (локальным) SSD).

1.) Сетевая загрузка

В коде, упомянутом выше: System.IO.FileInfo[] files2 = di.EnumerateFiles("*.jpg", SearchOption.AllDirectories).ToArray(); - я использовал для перечисления di, а не di2 (di относится к общему сетевому ресурсу)

(так что Array.ConvertAll имел доступ к удаленным файлам)

Время выполнения «этого» подхода: 6с 414 мс

2.) Местная загрузка

Исправление этого привело к запуску 1 с 886 мс

3.) Загрузка с локальной резьбой

Однако моя функция ThreadedLoading уже была выполнена, поэтому я тоже попробовал:

0 с 543 мс - все еще почти в 4 раза быстрее на 8-ядерном с 4 физическими ядрами.

Вот код. Возможно, еще не пуленепробиваемый, но дает надежные и быстрые (расчетные) результаты.

использование:

 DateTime t1 = DateTime.Now;
//Image[] images = Array.ConvertAll(files2, file => Image.FromFile(file.FullName));
Image[] images = loadImagesThreaded(files2);
DateTime t2 = DateTime.Now;

TimeSpan ts = t2.Subtract(t1);
MessageBox.Show("Loading took: " + ts.Seconds + "s " + ts.Milliseconds + "ms"); //6s 414ms

Resources.ProductImageList_256.Images.AddRange(images);

Функция:

private Image[] loadImagesThreaded(FileInfo[] files)
{
    int threadCount = 8;
    int chunkSize = files.Length / threadCount; //Round about -  doesn't need to be precise. 
    Thread[] threads = new Thread[threadCount];
    Image[][] result = new Image[threadCount][];

    for (int i = 0; i < threadCount; i++)
    {
        FileInfo[] chunk;
        int lowerBound = i * chunkSize;
        if (i < threadCount - 1)
        {
            chunk = files.Skip(lowerBound).Take(chunkSize).ToArray();
        }
        else
        {
            //take the rest
            chunk = files.Skip(lowerBound).ToArray();
        }

        int j = i; 
        threads[i] = new Thread(() =>
        {
            result[j] = Array.ConvertAll(chunk, file => Image.FromFile(file.FullName));
        });
        threads[i].Start();
    }

    //wait for threads to finish. 
    Boolean oneAlive = true;
    while (oneAlive)
    {
        oneAlive = false;
        foreach (Thread t in threads)
        {
            if (t.IsAlive)
            {
                oneAlive = true;
                break;
            }
        }
    }

    //all done. 
    Image[] finalResult = new Image[files.Count()];
    for (int i = 0; i < threadCount; i++)
    {
        int lowerBound = i * chunkSize;
        result[i].CopyTo(finalResult, lowerBound);
    }
    return finalResult;
}

разве это не приятный пик при предварительной загрузке изображений? :-)


4.) Parallel.ForEach

По рекомендации Фелипе Рамоса я попробовал Parallel.ForEach. Это примерно на 40-50 мс быстрее, чем мое «многопоточное» решение (в основном получающееся 480-500 мс), но, возможно, более важный факт:

Я бы сказал, что это несложно и, следовательно, более надежно:

Image[] images = new Image[files2.Count()];
Parallel.ForEach(files2, (fileInfo, state, index) =>
{
    images[index] = Image.FromFile(fileInfo.FullName);
});

DateTime t2 = DateTime.Now;
TimeSpan ts = t2.Subtract(t1);
MessageBox.Show("Loading took: " + ts.Seconds + "s " + ts.Milliseconds + "ms");

Другие вопросы по теме