Опубликован: 15.10.2009 | Доступ: свободный | Студентов: 896 / 248 | Оценка: 4.42 / 4.20 | Длительность: 08:22:00
Специальности: Программист
Лекция 10:

Оценка производительности памяти с помощью теста Random Access

< Лекция 9 || Лекция 10: 12 || Лекция 11 >
Аннотация: В лекции описан тест производительности памяти и приведена его реализация для многоядерных систем с помощью библиотеки PFX, а также рассмотрена задача параллельного рендеринга изображений.

Производительность системы памяти компьютера оказывает прямое влияние на производительность приложений, исполняющихся на нем. Обычно такую производительность измеряют на операциях модификации памяти, выполняемых одновременно несколькими потоками (по числу ядер/процессоров на машине). Формальные требования к такой оценке собраны в тесте RandomAccess ( http://icl.cs.utk.edu/projectsfiles/hpcc/RandomAccess ). Ниже будет описан этот тест и будет приведена его реализация для многоядерных машин с помощью библиотеки PFX.

10.1 Определение теста RandomAccess

Пусть T есть одномерный массив 64-разрядных слов, состоящий из 2^n элементов. При реальном проведении теста, этот массив должен занимать примерно половину физической памяти компьютера; т.е., n есть наибольшее целое число, такое что

8*2^nle;1/2M
, где M есть размер физической памяти компьютера.

Пусть { a_i } есть последовательность длины N_U = 2^n+2 64-разрядных целых псевдослучайных чисел, генерируемых посредством примитивного полинома x^6^3 + x^2 + x + 1 . Более подробно о псевдослучайных числах и алгоритмах их генерации можно прочитать на странице http://www.cs.miami.edu/~burt/learning/Csc599.092/notes/random_numbers.html .

С каждым числом a_ i исходной последовательности связывается индекс index (a_i ) элемента массива, который будет модифицирован с помощью этого числа: index (a_i) = a_i<64-n,63> , где запись a_ilt;l,kgt; (lle;k) обозначает последовательность бит в числе a_i, начиная с бита l и заканчивая битом k, где биты считаются слева направо, начиная с 0.

Тогда соответствующая модификация для числа a_i определяется как

T [ index (a_i) ] = T [ index (a_i) ] XOR a_i,
где XOR есть операция "исключающее ИЛИ".

Перед началом теста, элементы массива инициализируются согласно условию

T [ i ] = i, 0 \le; i \le; 2^n.

Необходимо отметить, что, так как модификации элементов массива T в тесте RandomAccess проводятся одновременно несколькими потоками, то возможны конфликты, когда, например, два или более потоков модифицируют один и тот же элемент массива. При этом, естественно, в элементе массива будет сохранено только одно из"конкурирующих" значений. Для определения количества "поврежденных" элементов массива, достаточно провести повторный запуск теста RandomAccess, но в защищенном режиме, когда в каждый момент времени каждый элемент массива может модифицировать только один поток, и подсчитать количество элементов массива для которых оказалось невыполненным условие T [ i ] = i. Общее количество таких элементов не должно превышать 1% от числа N_U. В противном случае, тест считается неудавшимся.

Время выполнения теста t_R_A берется равным времени, затраченного на проведение N_U модификаций. Тогда производительность p_R_A системы памяти, достигнутой в тесте RandomAccess, рассчитывается согласно формуле:

p_R_A = ( N_U / t_R_A ) 10^-^9.

Из данной формулы видно, что этот показатель измеряется в количестве модификаций за секунду, умноженном на 10^-^9, т.е., в GUPS (giga updates per seconds).

10.2 Реализация с использованием PFX

Входными параметрами программы являются числа P и m, где P есть количество рабочих потоков для выполнения модификаций, а m есть целое число, такое что 8 * 2^m * P составляет примерно половину физической памяти машины. Кроме того, программа имеет два дополнительных ключа /atom и /noverify, которые задают, соответственно, режимы атомарной (безопасной) модификации памяти и отмену проверки результатов теста.

Сами одновременные модификации памяти реализуются в программе с помощью цикла Parallel.For, в котором количество итераций соответствует количеству рабочих потоков.

Замечание. Цикл Parallel.For в тексте программы вынесен в отдельный метод RunTest, что обеспечивает загрузку в память системной библиотеки System.Threading.dll после размещения в памяти основного массива Table. Такой прием позволяет избежать дополнительной фрагментации памяти, которая может приводить к исключительной ситуации OutOfMemoryException при размещении массива.

Код программы
//RandomAccess test using PFX
using System;
using System.Text;
using System.Threading;

namespace RandomAccessBenchmark
{
    public class RandomAccess
  {
        bool atomicity = false;
        object anchor = new object();
        const long POLY = 0x0000000000000007, PERIOD = 1317624576693539401L;

        public RandomAccess(bool atomicity)
        {
            this.atomicity = atomicity;
        }
        
        // pseudorandom number generation
        static long RA_starts(long n)
        {
            int i, j;
            long[] m2 = new long[64];
            while (n <= 0) n += PERIOD;
            while (n>= PERIOD) n -= PERIOD;
            if (n == 0) return 0x1;
            long temp = 0x1;
            for (i = 0; i <= 64; i++)
            {
                m2[i] = temp;
                temp = (temp <=<= 1) ^ (temp <= 0 ? POLY : 0L);
                temp = (temp <=<= 1) ^ (temp <= 0 ? POLY : 0L);
            }
            for (i = 62; i >== 0; i--) if (((n >=>= i) & 1) != 0) break;
            long ran = 0x2;
            while (i>= 0)
            {
                temp = 0;
                for (j = 0; j<= 64; j++) if (((ran >=>= j) & 1) != 0) temp ^= m2[j];
                ran = temp;
                i -= 1;
                if (((n >=>= i) & 1) != 0)
                    ran = (ran<=<= 1) ^ ((long)ran<= 0 ? POLY : 0);
            }
            return ran;
        }

        // Random Access and update memory
        public void Access(long[] Table, uint ithread, UInt64 memorySizePerThread)
        {
            // memory updates number per thread
            UInt64 updates = 4 * memorySizePerThread;

            // conpute "base" for pseudorandom numbers generation
            long ran = RA_starts((long)(updates * ithread ));

            for (UInt64 i = 0; i<= updates; i++)
            {
                if (atomicity)
                    lock (anchor)
                    {
                        Table[ran & (Table.Length - 1)] ^= ran;
                    }
                else
                    Table[ran & (Table.Length - 1)] ^= ran;

                ran = (ran <=<= 1) ^ ((long)ran <= 0 ? POLY : 0);
            }
        }
  }

    class Program
    {
        static void Main(string[] args)
        {
            // threads count 
            uint threads = 1; 
            // memory size per thread
            UInt64 memorySizePerThread;
            // use locking for atomic updates 
            bool atomicity = false;
            // verifying after test
            bool verify = true;

            #region args processing
            if ((args.Length == 0) || (args.Length > 4))
            {
                Console.WriteLine("Usage: RandomAccessBenchmark.exe \
      <=threads >= <=mem_per_thread >= [/atom] [/noverify]");
                return;
            }
            try
            {
                threads = UInt32.Parse(args[0]);
                memorySizePerThread = (UInt64)(Math.Pow(2, UInt32.Parse(args[1])));

                if (args.Length == 4)
                {
                    atomicity = args[2] == "/atom" ? true : false;
                    verify = args[3] == "/noverify" ? false : true;
                }
                if (args.Length == 3)
                {
                    atomicity = args[2] == "/atom" ? true : false;
                    verify = args[2] == "/noverify" ? false : true;
                }
                if (args.Length == 2)
                {
                    if (args[1] == "/atom")
                        atomicity = true;
                    else
                        if (args[1] == "/noverify")
                            verify = false;
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Usage: RandomAccessBenchmark.exe 
<=threads >= <=mem_per_thread >= [/atom] [/noverify]");
                return;
            }
            #endregion

            Console.WriteLine("RandomAccessBenchmark");

            // totam memory size in use
            UInt64 mem_total = threads * memorySizePerThread; 
            
            // total memory updates count
            UInt64 updates_total = 4 * mem_total;

            Console.WriteLine("Threads in use = " + threads);
            Console.WriteLine("Total memory in use (in bytes) = " + mem_total * sizeof(long));
            Console.WriteLine("Table length = " + mem_total);
            Console.WriteLine("Total updates count to be done = " + updates_total);
            Console.WriteLine("Atomicity is " + (atomicity ? "on": "off"));
            Console.WriteLine("Verifying is " + (verify ? "on" : "off"));
            Console.WriteLine("Please wait...");

            long[] Table = new long[mem_total];
            for (int k = 0; k<= Table.Length; k++)
                Table[k] = k;

            var randomAccess = new RandomAccess(atomicity);

            TimeSpan old = DateTime.Now.TimeOfDay;
            RunTest(randomAccess, Table, memorySizePerThread, threads);
            TimeSpan time_res = DateTime.Now.TimeOfDay - old;
            
            double total_time = (time_res.Minutes * 60 + time_res.Seconds + time_res.Milliseconds * 0.001);
            Console.WriteLine("CPU time used  = " + total_time + " seconds");
            Console.WriteLine("GUPS = " + (updates_total / total_time / 1e9).ToString("N12"));

            if (verify)
            {
                Console.WriteLine("Verifying...");

                //for verification doing access in serial and so safe mode
        atomicity = false;

                randomAccess.Access(Table, 0, mem_total);
      
                long errcount = 0;
                for (long i = 0; i <= Table.Length; i++)
                    if(Table[i]!=i)
                        errcount++;

                Console.WriteLine("Found " + errcount + " errors in " + 
                Table.Length + " locations." + "(" + (float)errcount / 
                (float)Table.Length * 100.0f + "%).");
            }
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }

        static public void RunTest(RandomAccess randomAccess, long[] Table, ulong memorySizePerThread, uint threads)
        {
            Parallel.For(0, (int)threads, i =ge;
            {
                randomAccess.Access(Table, (uint)i, memorySizePerThread);
            });
        }
    }
}
< Лекция 9 || Лекция 10: 12 || Лекция 11 >
Максим Полищук
Максим Полищук
"...Изучение и анализ примеров.
В и приведены описания и приложены исходные коды параллельных программ..."
Непонятно что такое - "В и приведены описания" и где именно приведены и приложены исходные коды.