Archive for 2012年1月

Compiler Intrinsics

2012年1月19日

http://msdn.microsoft.com/ja-jp/library/26td21ds.aspx

MSDNが英文だけなので、要点だけ。

An intrinsic is often faster than the equivalent inline assembly, because
Intrinsics はしばしば同等のインラインアセンブラーより早いことがあるですと?

The intrinsics are required on 64-bit architectures where inline assembly is not supported.
おまけに、インラインアセンブラは64bit Architecture ではサポートされていない。

ヘッダーファイル: Intrin.h

#pragma intrinsic

または、

/Oi スイッチ

ということで、インラインアセンブラーの前に Intrinsics 。

広告

C# + Native C での高速化

2012年1月19日

image_thumb_thumb_thumb

前回、C# 、C++/CLI (マネージドコード)で実行してみたが、次に Native での実行と比較してみた。

ちょっと大きめの画像での実行結果は次の表のとおり。

番号 説明 C# (秒) C++/(秒) C# + Native C(秒)
1 Debug 8.1 4.3 2.9
2 Release 4.8(コードの最適化 Off) 3.7: 最適化無効 (/0d) 2.1: 最適化無効 (/0d)
3 Release 4.5 (コードの最適化 On) 2.7 : 最大限の最適化 (/Ox) 0.5: 最大限の最適化 (/Ox) + SSE2

コード自体は、C#/C++/Native C とほとんど同じ素直にループを回すロジックだが、速度的には10倍近い差が出ている。

Managed/Unmanaged 間のデータ交換はこちらを参考。

http://msdn.microsoft.com/ja-jp/library/ms146625(v=vs.80).aspx

Compiler Intrinsics

2012年1月18日

ようやく Native C++/C/Intrinsics /Asm/まで降りてきたが深い・・・

http://msdn.microsoft.com/ja-jp/library/26td21ds.aspx

また、明日潜りに来くる。

そういえば、Intel AVXマニュアル700ページ超って・・・><

Native C/C++ と Managed のはざまで

2012年1月18日

Native のC++ラッパーhttp://www.atmarkit.co.jp/fdotnet/special/vcppinvista01/vcppinvista01_03.html

メモっておこうっと。

C++/CLI マネージドのコンパイル時の最適化の効果

2012年1月15日

image_thumb_thumb

前回、C# で実行させたが、C++/CLI (マネージドコード)での実行と比較してみた。

ちょっと大きめの画像での実行結果は次の表のとおり。

番号 説明 C# (秒) C++/(秒)
1 Debug 8.1 4.3
2 Release 4.8(コードの最適化 Off) 3.7: 最適化無効 (/0d)
3 Release 4.5 (コードの最適化 On) 2.7 : 最大限の最適化 (/Ox)

C++/CLI だと、コンパイル時の最適化が選択できる。デフォルトだと、最適化が無効になっているが、これを最大限の最適化にすると、2倍近い速度が出る。

同じMSILのC++/CLIでは最適化オプションが選択できて、おまけにそこそこ高速化できるのは、納得できないなぁ・・・

image

ところで、C/C++ コード生成で、”ストリーミング SIMD 拡張機能 2 (/arch:SSE2) (/arch:SSE2)”オプションを設定すると、次のエラーになる。

エラー    1    error D8016: コマンド ライン オプション ‘/clr’ と ‘/arch:SSE2’ は同時に指定できません  

CLRとSSE拡張命令は同時につかえない。これは、MSILを使っているので当たり前といえば当たり前か。

インラインアセンブラでの文字列の引数、返り値

2012年1月14日

インラインアセンブラでの文字列の引数、返り値の扱いの解説がどこにもなくって、コンパイラが生成するアセンブラから解読して、ようやく動くようになった。ちょっとわかりにくかったので、メモっておく。

渡された char* に書き込む場合

void TestClass::GetVenderSignature(char* vender_sig)
{
    __asm
    {
           mov eax, 0;  ;    /* Vender Signature を取得するindex */
           cpuid;                                 ;             /* CPUID を実行*/
           mov eax, vender_sig
           mov dword ptr [eax], ebx;       ;   /* 最初の4文字*/
           mov dword ptr [eax + 4], edx;   ;   /* 次の4文字*/
           mov dword ptr [eax + 8], ecx;   ;   /* 最後の4文字*/
           mov byte ptr [eax + 12], 0;     ;   /* */  
    }
    return;
}

char*で返す場合

char* TestClass::GetVenderSignature()
{
    __asm
    {
           mov eax, 0;  ;    /* Vender Signature を取得するindex */
           cpuid;                                 ;             /* CPUID を実行*/
           mov eax, vender_sig
           mov dword ptr [eax], ebx;       ;   /* 最初の4文字*/
           mov dword ptr [eax + 4], edx;   ;   /* 次の4文字*/
           mov dword ptr [eax + 8], ecx;   ;   /* 最後の4文字*/
           mov byte ptr [eax + 12], 0;     ;   /* */  
           mov eax, dword ptr vender_sig
    }
    return;
}

[DllImport(“NativeCode.dll”)]
static extern IntPtr GetVenderSignature( );

IntPtr unmanagedBuf = Marshal.AllocHGlobal(32);
GetVenderSignature(unmanagedBuf);
string ansiStr = Marshal.PtrToStringAnsi(unmanagedBuf);
textBoxResult.Text = ansiStr;
Marshal.FreeHGlobal(unmanagedBuf);

Using and Preserving Registers in Inline Assembly

2012年1月9日

http://msdn.microsoft.com/ja-jp/library/k1a8ss06.aspx

より、ポイントだけメモ。

When using __asm to write assembly language in C/C++ functions, you don’t need to preserve the EAX, EBX, ECX, EDX, ESI, or EDI registers.

You should preserve other registers you use (such as DS, SS, SP, BP, and flags registers) for the scope of the __asm block. You should preserve the ESP and EBP registers unless you have some reason to change them (to switch stacks, for example).

SSE サポート状況

2012年1月7日

インテル 64 アーキテクチャーおよび IA-32 アーキテクチャー最適化リファレンス・マニュアルがわかりにくいので、まとめメモ。

アーキテクチャ 備考 SSE
SSE2
SSE3 SSSE3 SSE4.1 SSE4.2 AESNI PCLMULQDQ
AVX
Pen4, Xeon, PenM              
インテルCore              
Pen4 90nm HT            
Core Solo, Duo            
拡張版インテルCore        
Nehalem Xeon, 初代 Core i7, i5
45n
   
Westmere Xeon, 初代Core i7, i5
32n
 
Sandy Bridge 第2世代Core i753

コード上での高速化 V.S. Parallel での高速化

2012年1月7日

image_thumb

大きめの画像3072×2304 Color画像を、Core i7 860 の4Core 8Thread を使用して、3×3の8近傍ラプラシアンフィルターを実行した場合、シリアル処理で約4.66秒を必要となった。これをパラレル実行することで、約4倍の1.11秒まで高速化することができた。

この時のコードは、カラーのループ、フィルターのループを素直?にfor ループで実装している。また、フィルターもfloatによる積和演算を行っているため、まだコード上の高速化の余地は残っている。では、さらにコード上での高速化を図った場合にどこまで高速化できるのか、興味がわいてきたので、実際に測定してみた。

結果は次の表のとおり。

番号 説明 シリアル実行(秒) パラレル実行(秒)
1 素直にフィルターを実装 4.66 1.11
2 フィルターの2重for ループを展開 3.04 0.81
3 float による積和をint 加減算とシフトに変更 2.11 0.51
4 Color のループを展開 1.60 0.40

シリアル実行の場合は、コード上でカラー、フィルターの3重ループを展開し、float の積和をint の加減算に変更することにより、4.66秒が1.60秒と、約3倍の高速化が可能という結果となった。

さらに、これを行単位でのパラレル実行した場合、なにも考慮しないシリアル実行の場合の4.66秒に対して、最終的に0.40秒と、約10倍の高速化となった。

注目すべき点は、ループの展開や積和を加減算に変更するなどといった、従来型のソフトウェアチューニングが約3倍の高速化だったのに対して、単純にパラレル展開して、4Core 8Thread をフルに使うことにより、非常に簡単に約4倍の高速化が得られている点である。

すでに4Core,6Coreなどのメニーコアが手軽に入手できるようになっており、コードをチューニングするよりは、わかりやすいコードを維持したまま、パラレル展開するほうが望ましいという時代が到来したということだろう。

ただし、今回の画像処理のようなパラレル処理に向いた処理は限られているので、どのような場合に並列処理を適用すべきか、慎重に判断すべきである。また、処理が複雑になれば思わぬデッドロックやデータ競合が発生することもあり、中途半端な知識で並列処理を適用するのはやめたほうがいい。

一応、フィルターのコード(No.1とNo.4)をさらしておく。

#1のコード
private void FilterOneLine(int y)
{
    int str = Width * BytesPerPixel;
    for (int x = 1; x < Width – 1; x += 1)
    {
        // 各色ごとに
        for (int col = 0; col < BytesPerPixel; col++)
        {
            int index = str * y + BytesPerPixel * x + col;
            if (col == 3)
            {
                Result[index] = 255;
                continue;
            }

            float v = 0f;

            for (int yy = -1; yy <= 1; yy++)
                for (int xx = -1; xx <= 1; xx++)
                {
                    int position = str * (y + yy) + BytesPerPixel * (x + xx) + col;
                    float filterValue = filter[yy + 1, xx + 1];

                    v = v + Source[position] * filterValue;
                }
            v = Math.Abs(v);
            v = (v > 10) ? 255 : 0;
            Result[index] = (byte)v;
        }
    }
}

#4のコード

private void FilterOneLine3(int y)
{
     int str = Width * BytesPerPixel;
     for (int x = 1; x < Width – 1; x += 1)
     {
         int index = 0;
         int v;

         index = str * y + BytesPerPixel * x;

         // col == 0
         v =
             Source[index – str – BytesPerPixel] +
             Source[index – str] +
             Source[index – str + BytesPerPixel] +
             Source[index – BytesPerPixel] +
             -8 * Source[index] +
             Source[index + BytesPerPixel] +
             Source[index + str – BytesPerPixel] +
             Source[index + str] +
             Source[index + str + BytesPerPixel];

         v = Math.Abs(v / 8);
         v = (v > 10) ? 255 : 0;
         Result[index] = (byte)v;

         // col == 1
         index++;
         v =
             Source[index – str – BytesPerPixel] +
             Source[index – str] +
             Source[index – str + BytesPerPixel] +
             Source[index – BytesPerPixel] +
             -8 * Source[index] +
             Source[index + BytesPerPixel] +
             Source[index + str – BytesPerPixel] +
             Source[index + str] +
             Source[index + str + BytesPerPixel];

         v = Math.Abs(v / 8);
         v = (v > 10) ? 255 : 0;
         Result[index] = (byte)v;

         // col == 2
         index++;
         v =
             Source[index – str – BytesPerPixel] +
             Source[index – str] +
             Source[index – str + BytesPerPixel] +
             Source[index – BytesPerPixel] +
             -8 * Source[index] +
             Source[index + BytesPerPixel] +
             Source[index + str – BytesPerPixel] +
             Source[index + str] +
             Source[index + str + BytesPerPixel];

         v = Math.Abs(v / 8);
         v = (v > 10) ? 255 : 0;
         Result[index] = (byte)v;

         // col == 3
         index++;
         Result[index] = 255;
     }
}

WriteableBitmap で、ラプラシアン フィルターを並列実行

2012年1月5日

WriteableBitmapで、画像処理をする場合の例。ここでは、ラプラシアンフィルタによる8近傍空間2次微分を計算し、輪郭を検出している。

image

GetBitmap, SetBitmap を使用した例では、GUIへの書き込み処理をメインスレッドで行う必要があり、マルチスレッド、マルチコアでの並列処理が難しい。そこで、WriteableBitmapを使用して、画像データをbyte列として抽出し、そのデータに対して、並列に画像処理を行い、Bitmapに書き戻すように処理をすることにより、マルチコアによる並列処理の効果も確認してみた。

Core i7 860 の4Core 8Thread で、SIDBA GIRL 256×256 Color  が0.043秒(シリアル)が0.014秒(パラレル)で約3倍高速。

image

Core i7 860 の4Core 8Thread で、SIDBA Mandrill 512×512 Colorで、0.17秒(シリアル)が0.05秒(パラレル)で、約3倍高速。

image

もう少し大きめの画像で、Core i7 860 の4Core 8Thread で、3072×2304 Colorで、4.66秒(シリアル)が1.11秒(パラレル)で、約4倍高速。

シリアル実行時のCPU使用状況

image

Parallel.For 実行時のCPU使用状況

image

フィルター処理自体は、高速化を意識せずに素直に処理している。512×512程度のカラー画像に対して、3×3程度のフィルターは、ほぼリアルタイム処理が可能と思われる。

C# Parallel を使ってみたのは2度目だが、言語レベルでマルチコアがサポートされているのは、素晴らしすぎる。

さらに、マトリックス演算(積和)を行わず、加算だけで処理するような高速化や、フィルターの展開によるループの削減などの、コード上のチューニングだけでも、かなりの高速化が期待できると思う。また、Intel SIMD(MMX) を適用できるとおもわれるが、どの程度チューニングできるのか、機会があったら試してみたい。

MainWindow.Xaml

<Window x:Class=”WritableBitmap.MainWindow”
        xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
        xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”
        Title=”MainWindow” Height=”406″ Width=”525″>
    <Grid>
        <Viewbox Margin=”0,64,0,0″>
        <StackPanel Orientation=”Horizontal” Margin=”58,79,91,58″>
        <Viewbox >
            <Image x:Name=”SourceImage” VerticalAlignment=”Stretch” HorizontalAlignment=”Stretch” Stretch=”UniformToFill”  />
        </Viewbox>
        <Viewbox >
            <Image x:Name=”ResultImage” VerticalAlignment=”Stretch” HorizontalAlignment=”Stretch” Stretch=”UniformToFill”  />
        </Viewbox>
        </StackPanel>
        </Viewbox>
        <Button Content=”Load Image” Height=”30″ HorizontalAlignment=”Left” Margin=”58,13,0,0″ Name=”button1″ VerticalAlignment=”Top” Width=”95″ Click=”button1_Click” />
        <Button Content=”Do in Serial” Height=”30″ HorizontalAlignment=”Left” Margin=”231,13,0,0″ Name=”button2″ VerticalAlignment=”Top” Width=”95″ Click=”button2_Click” />
        <Button Content=”Do in Parallel” Height=”30″ HorizontalAlignment=”Left” Margin=”347,13,0,0″ Name=”button3″ VerticalAlignment=”Top” Width=”95″ Click=”button3_Click” />
    </Grid>
</Window>

MainWindow.Xaml.cs

using System;
using System.Windows;
using System.Windows.Media.Imaging;
using Microsoft.Win32;

namespace WritableBitmap
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            OnFileOpen();
        }

        WriteableBitmap srcWritableBitmap;

        void OnFileOpen()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            dlg.Filter = “Bitmap files|*.bmp;*.png;*.jpeg;*.jpg;*.gif;*.tiff;*.tif;*.wdp|” +
                         “BMP files (*.bmp)|*.bmp|” +
                         “PNG files (*.png)|*.png|” +
                         “JPEG files (*.jpeg, *.jpg)|*.jpeg;*.jpg|” +
                         “GIF files (*.gif)|*.gif|” +
                         “TIFF files (*.tiff, *.tif)|*.tiff;*.tif|” +
                         “Windows Media Player files (*.wdp)|*.wdp|” +
                         “All files (*.*)|*.*”;

            if ((bool)dlg.ShowDialog())
            {
                SourceImage.Source = null;
                srcWritableBitmap = null;
                BitmapFrame frame = null;

                try
                {
                    frame = BitmapFrame.Create(new Uri(dlg.FileName), BitmapCreateOptions.None,
                                                                      BitmapCacheOption.None);
                }
                catch (Exception exc)
                {
                    MessageBox.Show(exc.Message, Title);
                    return;
                }

                srcWritableBitmap = new WriteableBitmap(frame);

                if (srcWritableBitmap.Format.BitsPerPixel != 8 &&
                    srcWritableBitmap.Format.BitsPerPixel != 24 &&
                    srcWritableBitmap.Format.BitsPerPixel != 32)
                {
                    MessageBox.Show(“Bitmap must have 8 or 24 bits or 32 bits per pixel”, Title);
                    srcWritableBitmap = null;
                    return;
                }

                SourceImage.Source = srcWritableBitmap;
            }
        }

        private void buttonExecInSerial_Click(object sender, RoutedEventArgs e)
        {

            Filter filter = new Filter();

            filter.SetParameters(
                srcWritableBitmap.PixelWidth,
                srcWritableBitmap.PixelHeight,
                srcWritableBitmap.Format.BitsPerPixel / 8);

            srcWritableBitmap.CopyPixels(filter.Source, GetStride(), 0);

            filter.ExecuteInSerial();

            WriteableBitmap resWritableBitmap = srcWritableBitmap.Clone();
            ResultImage.Source = resWritableBitmap;

            resWritableBitmap.WritePixels(filter.Rect, filter.Result, filter.Stride, 0);
        }

        private void buttonExecInParallel_Click(object sender, RoutedEventArgs e)
        {
            Filter filter = new Filter();

            filter.SetParameters(
                srcWritableBitmap.PixelWidth,
                srcWritableBitmap.PixelHeight,
                srcWritableBitmap.Format.BitsPerPixel / 8);

            srcWritableBitmap.CopyPixels(filter.Source, GetStride(), 0);

            filter.ExecuteInParallel();

            WriteableBitmap resWritableBitmap = srcWritableBitmap.Clone();
            ResultImage.Source = resWritableBitmap;
           
            resWritableBitmap.WritePixels(filter.Rect, filter.Result, filter.Stride, 0);
        }

        private int GetStride()
        {
            int stride = srcWritableBitmap.PixelWidth * srcWritableBitmap.Format.BitsPerPixel / 8;
            return stride;
        }
    }
}

Filter.cs

#define STOPWATCH

using System;
using System.Windows;
using System.Threading.Tasks;

namespace WritableBitmap
{
    class Filter
    {
        float[,] filter = {
                          { -1 / 8f, -1 / 8f, -1 / 8f },
                          { -1 / 8f,  8 / 8f, -1 / 8f },
                          { -1 / 8f, -1 / 8f, -1 / 8f }
                          };

        private int _width, _height, _bytesPerPixel;
        public int Width
        {
            get { return this._width; }
            private set { this._width = value; }
        }
        public int Height
        {
            get { return this._height; }
            private set { this._height = value; }
        }
        public int BytesPerPixel
        {
            get { return this._bytesPerPixel; }
            private set { this._bytesPerPixel = value; }
        }
        public Int32Rect Rect { get; set; }
        public int Stride { get; set; }
        public byte[] Source { get; set; }
        public byte[] Result { get; set; }

        public void SetParameters(int width, int height, int bytesPerPixel)
        {
            Width = width;
            Height = height;
            BytesPerPixel = bytesPerPixel;
            Rect = new Int32Rect(0, 0, Width, Height);
            Stride = Width * BytesPerPixel;
            Source = new byte[Width * BytesPerPixel * Height];
            Result = new byte[Width * BytesPerPixel * Height];
        }

        public void ExecuteInSerial()
        {
#if STOPWATCH
            DateTime dtStart = DateTime.Now;
#endif

            for(int y = 1; y < Height – 1; y++)
                FilterOneLine(y);

#if STOPWATCH
            DateTime dtEnd = DateTime.Now;
            Console.WriteLine((dtEnd – dtStart).ToString());
#endif
        }
       
        public void ExecuteInParallel()
        {
#if STOPWATCH
            DateTime dtStart = DateTime.Now;
#endif

            Parallel.For(1, Height-1, FilterOneLine);

#if STOPWATCH
            DateTime dtEnd = DateTime.Now;
            Console.WriteLine((dtEnd-dtStart).ToString());
#endif
        }

        private void FilterOneLine(int y)
        {
            int str = Width * BytesPerPixel;
            for (int x = 1; x < Width – 1; x += 1)
            {
                // 各色ごとに
                for (int col = 0; col < BytesPerPixel; col++)
                {
                    int index = str * y + BytesPerPixel * x + col;
                    if (col == 3)
                    {
                        Result[index] = 255;
                        continue;
                    }

                    float v = 0f;

                    for (int yy = -1; yy <= 1; yy++)
                        for (int xx = -1; xx <= 1; xx++)
                        {
                            int position = str * (y + yy) + BytesPerPixel * (x + xx) + col;
                            float filterValue = filter[yy + 1, xx + 1];

                            v = v + Source[position] * filterValue;
                        }
                    v = Math.Abs(v);
                    v = (v > 10) ? 255 : 0;
                    Result[index] = (byte)v;
                }
            }
        }
    }
}