c # Image.FromFile - Массовая операция? - PullRequest
0 голосов
/ 08 октября 2018

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

Просмотр списка 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-основанныминдекс в соответствующей карте изображения и не требует времени для генерации.

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

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

1 Ответ

0 голосов
/ 08 октября 2018

Хорошо, я написал функцию, которая будет выполнять работу, распределенную по нескольким потокам, а затем обнаружил «ошибку» в моем коде.(Тем не менее, использование многопоточности очень помогает, поэтому 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 -функция уже была выполнена, поэтому я тоже попробовал:

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

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

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

 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;
}

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

enter image description here


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");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...