マルチスレッド化グレースケール処理を改良してみた

Athlon64x2にて、行ったマルチスレッドグレースケール処理は、こちら



別系の仕事でやっていた、画像処理プログラム

マルチスレッドでもちろん書いたけど、いまいち、速度が上がらない

んで、つらつら、考えてみたら

スレッド間の終了同期を取るため、Thread.Sleep(1);を入れていたり
for(int j = 1; j <THREADCOUNT; j++)
{
    m_ThreadJobFixFL[j] = true;         //スレッド処理完了フラグ (0番以外は完了させておく)
    m_ThreadJobFL[j]    = false;        //スレッド処理開始フラグ(0番起動 以外は停止)
}
for(int i = 0; i <1000; i++)           //スレッド0番(480ライン処理)のみ起動
{
    m_ThreadJobFixFL[0] = false;        //スレッド処理完了フラグ (0番以外は完了させておく)
    m_ThreadJobFL[0] = true;            //スレッド処理開始フラグ(0番起動 以外は停止)
    while(IsThreadJobFix() == false )            //全スレッドの処理が完了するまで待機
    {
        Thread.Sleep(1);        //<----このあたりの話
    }
}

1msのスリープって、クアッド様の前では、あまりにも長い時間

この1msのために、演算能力、一部奪われていた予感

終了同期の取り方、いまいち、よろしくないんで、ちょいと書き換えてみたら
性能、極端に上がったんで、書いておこうか、と

条件は以下のとおり

CPU : Intel Core2Quad Q6600(2.4GHz)
Memory : 2GB
OS : Windows XP Pro
言語: Visual Studio 2005(C#2.0) + .Net FrameWork2.0


処理速度が上がっているので、
前回の1000枚のグレースケール処理から、4000枚に増やしてみた

あと、前回と同じように、1枚の画面を、4分割して、スレッド処理やろうとしたけど

速過ぎて(w、スレッド間の終了同期が取れない事態が発生

なので、アルゴリズムを変更

4000枚のうち、1スレッドに1000枚ずつ処理させるようにしてみたり

結果は以下のとおり

画像データ4000回の
グレースケール処理を実行
リリースコンパイル (参考)
Athlon64x2のときの時間
(4倍してあります)
640 * 480
24ビットピクセル

1スレッド
18,710ミリ秒 46,810ミリ秒
640 * 480
24ビットピクセル

2スレッド
9,640ミリ秒 31,370ミリ秒
640 * 480
24ビットピクセル

4スレッド
5,390ミリ秒 29,560ミリ秒
前のAthlon64x2時代より、えらく、処理速度上がってるなぁ。。。
Sleep()での終了同期は、あまりよろしくないですわ

マルチスレッドの処理は
リソース間同期も大変だけど、終了同期も大変だわ、こりゃ

高速化させたい処理にて、いかに効率よく、CPUにデータを食わすか
設計屋の腕の見せ所、ですな

コードは4スレッド対応版
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Windows.Forms;
using System.Threading; 
using System.Runtime.InteropServices;
using System.Diagnostics;
using tube4Capture.Objects;
namespace GrayScaleTest
{
    [StructLayout(LayoutKind.Sequential,Pack=4)]
    public struct Pixel24
    {
        public byte B;
        public byte G;
        public byte R;
    }

    public partial class Form1 : Form
    {

        private const int THREADCOUNT = 5;
        static public Form1 m_MyObj;            //スレッドへ情報を渡すため、自分自身のインスタンスをセット
        private Bitmap m_ImageBackup;   // 一時データ
        private Bitmap m_bmapIn;	    // 入力データ
        private Bitmap m_bmapOut;      // 出力データ
        private bool    m_ThreadOnFL;
        private bool[]  m_ThreadJobFL;
        private bool[]  m_ThreadJobFixFL;
        private Thread[] m_Thread;
        private IntPtr m_SouPT;
        private IntPtr m_DesPT;
        private int m_Width;            // Image サイズ
        private int m_Height;           // Image サイズ
		private int m_ThreadFixCount;
		private bool m_CalcJobEnd;
		private	DateTime		m_dt;
        private Bitmap			m_bd;
        private Bitmap			m_bs; 
        private BitmapData		m_bDataInS; 
        private BitmapData		m_bDataInD; 
        private IntPtr			m_Scan0_InS; 		// ピクセルデータの開始アドレス
        private IntPtr			m_Scan0_InD; 		// ピクセルデータの開始アドレス
        private IntPtr			m_Scan0_InS2; 		// 新しいバッファ1
        private IntPtr			m_Scan0_InD2; 		// 新しいバッファ2
        public Form1()
        {
            InitializeComponent();
            m_MyObj = this;
        }

        private void Form1_Load(object sender, EventArgs e)
        {

            pictureBox1.Image = null;
            m_ThreadOnFL = true;
            m_ThreadJobFL    = new bool[THREADCOUNT];
            m_ThreadJobFixFL = new bool[THREADCOUNT];
            for (int i = 0; i < THREADCOUNT; i++)
            {
                m_ThreadJobFL[i]    = false;
                m_ThreadJobFixFL[i] = false;
            }


            m_Thread = new Thread[THREADCOUNT];

            m_Thread[0] = new Thread(new ThreadStart(ThreadMethod0)); 
            m_Thread[1] = new Thread(new ThreadStart(ThreadMethod1)); 
            m_Thread[2] = new Thread(new ThreadStart(ThreadMethod2)); 
            m_Thread[3] = new Thread(new ThreadStart(ThreadMethod3)); 
            m_Thread[4] = new Thread(new ThreadStart(ThreadMethod4)); 
            m_Thread[0].Name = "画像処理スレッド0";
            m_Thread[1].Name = "画像処理スレッド1";
            m_Thread[2].Name = "画像処理スレッド2";
            m_Thread[3].Name = "画像処理スレッド3";
            m_Thread[4].Name = "画像処理スレッド4";

            m_Thread[0].Start();
            m_Thread[1].Start();
            m_Thread[2].Start();
            m_Thread[3].Start();
            m_Thread[4].Start();

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            m_ThreadOnFL = false;
            for(int i = 0; i < 200; i++)
            {
                Application.DoEvents();
                Thread.Sleep(1);
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string FileName;
            if (openFileDialog1.ShowDialog() != DialogResult.OK)
            {
                return;
            }
            FileName = openFileDialog1.FileName;
            m_ImageBackup = new Bitmap(FileName);
            m_Width = m_ImageBackup.Width;
            m_Height = m_ImageBackup.Height;
            m_bmapOut = new Bitmap(m_Width, m_Height);
            m_bmapIn = m_ImageBackup;
            pictureBox1.Image = m_bmapIn;
        }

        private void JobCore_Click(object sender, EventArgs e)
        {
            m_dt = DateTime.Now;  
            int min  = m_dt.Minute;
            int sec  = m_dt.Second;
            int mili = m_dt.Millisecond;  
            lblStart.Text = "";
            lblEnd.Text   = "";
            lblJob.Text   = "";

            lblStart.Text = "開始時間:" + min.ToString() + "_" + sec.ToString() + "_" + mili.ToString();
            Refresh();

            m_bd = pictureBox1.Image as Bitmap;
            m_bs = new Bitmap(m_bmapIn);
            m_bDataInS = m_bs.LockBits(new Rectangle(0, 0, m_bs.Width, m_bs.Height),
                                ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            m_Scan0_InS = m_bDataInS.Scan0;		// ピクセルデータの開始アドレス

            m_bDataInD = m_bd.LockBits(new Rectangle(0, 0, m_bd.Width, m_bd.Height),
                                ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
            m_Scan0_InD = m_bDataInD.Scan0;		// ピクセルデータの開始アドレス


            m_Scan0_InS2 = CommonMethedMemory.MemAlloc(m_bs.Width * m_bs.Height * 3); 		// 新しいバッファ1
            m_Scan0_InD2 = CommonMethedMemory.MemAlloc(m_bd.Width * m_bd.Height * 3); 		// 新しいバッファ2
            CommonMethedMemory.MemCopy(m_Scan0_InS2, m_Scan0_InS, m_bs.Width * m_bs.Height * 3);
            m_SouPT  = m_Scan0_InS2;
            m_DesPT  = m_Scan0_InD2;
            m_Width  = m_bd.Width;
            m_Height = m_bd.Height;

			m_ThreadFixCount = 0;
			m_CalcJobEnd = false;
			tmrThreadEndCheck.Enabled = true;
			if (sender == button2)
            {
                for(int j = 1; j < THREADCOUNT; j++)
                {
                    m_ThreadJobFixFL[j] = true;         //スレッド処理完了フラグ (0番以外は完了させておく)
                    m_ThreadJobFL[j]    = false;        //スレッド処理開始フラグ(0番起動 以外は停止)
                }
                m_ThreadJobFixFL[0] = false;			//スレッド処理完了フラグ (0番)
                m_ThreadJobFL[0] = true;				//スレッド処理開始フラグ(0番)
            }
            else
            {

				m_ThreadJobFixFL[0] = true;				//スレッド処理完了フラグ (0番は完了させておく)
                m_ThreadJobFL[0]    = false;			//スレッド処理開始フラグ(0番停止 以外は起動)

                for(int j = 1; j < THREADCOUNT; j++)
                {
                    m_ThreadJobFixFL[j] = false;        //スレッド処理完了フラグ (0番以外)
                    m_ThreadJobFL[j] = true;            //スレッド処理開始フラグ(0番以外)
                }
            }

        }

		//スレッド処理終了時の後始末系タイマ
		private void tmrThreadEndCheck_Tick(object sender, EventArgs e)
		{
			if (m_CalcJobEnd == false)		//スレッドが終わるまでは、タイマはすぐ抜ける
			{
				return;
			}

			tmrThreadEndCheck.Enabled = false;

            CommonMethedMemory.MemCopy(m_Scan0_InD, m_Scan0_InD2, m_bs.Width * m_bs.Height * 3);
            CommonMethedMemory.MemFree(m_Scan0_InS2);
            CommonMethedMemory.MemFree(m_Scan0_InD2);
            m_bd.UnlockBits(m_bDataInD);
            m_bs.UnlockBits(m_bDataInS);

            DateTime dt2 = DateTime.Now;  
            int min  = dt2.Minute;
            int sec  = dt2.Second;
            int mili = dt2.Millisecond;  
            lblEnd.Text = "終了時間:" + min.ToString() + "_" + sec.ToString() + "_" + mili.ToString();;
            TimeSpan tm = dt2.Subtract(m_dt);
            lblJob.Text = "経過時間:" + tm.TotalSeconds.ToString();
            Refresh();
		}

        //すべてのスレッドの処理が完了したらTrue
        private bool IsThreadJobFix()
        {
            for(int i = 0; i < THREADCOUNT; i++)
            {
                if (m_ThreadJobFixFL[i] == false)
                {
                    return false;
                }
            }

            return true;
        }
        
        private static void ThreadMethod0() { m_MyObj.ThreadMainJob0(); }	//スレッド向けコールバック関数(0番)
        private static void ThreadMethod1() { m_MyObj.ThreadMainJob1(); }	//スレッド向けコールバック関数(1番)
        private static void ThreadMethod2() { m_MyObj.ThreadMainJob2(); }	//スレッド向けコールバック関数(2番)
        private static void ThreadMethod3() { m_MyObj.ThreadMainJob3(); }	//スレッド向けコールバック関数(3番)
        private static void ThreadMethod4() { m_MyObj.ThreadMainJob4(); }	//スレッド向けコールバック関数(4番)
        //スレッドによるグレースケール処理
        private void ThreadMainJob0()
        {
            while(m_ThreadOnFL == true)
            {
                if (m_ThreadJobFL[0] == false)
                {
                    Thread.Sleep(1);
                    continue;
                }

				for(int i = 0; i < 4000; i++)
				{
					GrayScaleB(0, 480, m_SouPT, m_DesPT, m_Width, m_Height);
				}

                m_ThreadJobFL[0]      = false;
                m_ThreadJobFixFL[0]   = true;
				ThreadJobFixSingle(0);
            }
        }

        private void ThreadMainJob1()
        {
            while(m_ThreadOnFL == true)
            {
                if (m_ThreadJobFL[1] == false)
                {
                    Thread.Sleep(1);
                    continue;
                }

				for(int i = 0; i < 1000; i++)
				{
					GrayScaleB(0, 480, m_SouPT, m_DesPT, m_Width, m_Height);
				}

                m_ThreadJobFL[1]      = false;
                m_ThreadJobFixFL[1]   = true;
				ThreadJobFixMulti(1);
            }
        }

        private void ThreadMainJob2()
        {
            while(m_ThreadOnFL == true)
            {
                if (m_ThreadJobFL[2] == false)
                {
                    Thread.Sleep(1);
                    continue;
                }

				for(int i = 1000; i < 2000; i++)
				{
					GrayScaleB(0, 480, m_SouPT, m_DesPT, m_Width, m_Height);
				}

                m_ThreadJobFL[2]      = false;
                m_ThreadJobFixFL[2]   = true;
				ThreadJobFixMulti(2);
            }
        }

        private void ThreadMainJob3()
        {
            while(m_ThreadOnFL == true)
            {
                if (m_ThreadJobFL[3] == false)
                {
                    Thread.Sleep(1);
                    continue;
                }

				for(int i = 2000; i < 3000; i++)
				{
					GrayScaleB(0, 480, m_SouPT, m_DesPT, m_Width, m_Height);
				}

                m_ThreadJobFL[3]      = false;
                m_ThreadJobFixFL[3]   = true;
				ThreadJobFixMulti(3);
            }
        }

        private void ThreadMainJob4()
        {
            while(m_ThreadOnFL == true)
            {
                if (m_ThreadJobFL[4] == false)
                {
                    Thread.Sleep(1);
                    continue;
                }

				for(int i = 3000; i < 4000; i++)
				{
					GrayScaleB(0, 480, m_SouPT, m_DesPT, m_Width, m_Height);
				}

                m_ThreadJobFL[4]      = false;
                m_ThreadJobFixFL[4]   = true;
				ThreadJobFixMulti(4);
            }
        }

		//スレッド演算終了時に呼び出される(シングル)
		private void ThreadJobFixSingle(int threadNo)
		{
			m_CalcJobEnd = true;		//シングルスレッド処理
		}

		//スレッド演算終了時に呼び出される(マルチ)
		private void ThreadJobFixMulti(int threadNo)
		{
			m_ThreadFixCount++;			//マルチスレッド処理

			if (m_ThreadFixCount < 4)	//4スレッドが終了するまで最終処理を行わない
			{
				return;
			}

			m_CalcJobEnd = true;
		}

		//グレースケール処理
        public void GrayScaleB(int startY, int endY, IntPtr ptS, IntPtr ptD, int tmpWidth, int tmpHeight)
        {
            int x, y;
            int index;
            byte PixelData;
            byte red, green, blue;

            int scanl = tmpWidth * 3;
            unsafe
            {
                byte* ps = (byte*)ptS;
                byte* pd = (byte*)ptD;
                for (y = startY; y < endY; y++)
                {
                    for (x = 0; x < scanl; x += 3)
                    {
                        index = (y * scanl) + x;

                        blue  = ps[index ];
                        green = ps[index + 1];
                        red   = ps[index + 2];
                        PixelData = (byte)(0.299f * ((float)red) + 0.587f * ((float)green) + 0.114f * ((float)blue));

                        pd[index]     = PixelData;
                        pd[index + 1] = PixelData;
                        pd[index + 2] = PixelData;
                    }
                }
            }
        }
    }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください