Rails3でjQueryを使う方法

前回の記事の続きでjQueryも使えるようにする。

インストール

Gemfileにjquery-railsを追加

gem 'jquery-rails'

jQueryをインストール。(prototype.js関連のファイルが削除される)

$ bundle install
$ rails g jquery:install
      remove  public/javascripts/controls.js
      remove  public/javascripts/dragdrop.js
      remove  public/javascripts/effects.js
      remove  public/javascripts/prototype.js
      create  public/javascripts/jquery.min.js
      create  public/javascripts/jquery.js
    conflict  public/javascripts/rails.js
   Overwrite  public/javascripts/rails.js? (enter "h" for help) [Ynaqdh] y
       force  public/javascripts/rails.js

プロジェクト作成時にprototype.jsが不要だと分かっている場合は-Jで生成を抑止できる。

$ rails new APP_NAME -J

動作確認

views/samples/index.html.hamlに以下の記述を追加。

  3 %table#fade_table
(snip)
 19 :css
 20   #fade_table {
 21     display: none;
 22   }
 23 :javascript
 24   window.onLoad = (function(){
 25     $('#fade_table').fadeIn('slow');
 26   })();

これでサーバを再起動して/samplesにアクセスするとデータのtableがフェードインしながら表示されます。
内の読み込むjsもprototypeからjQueryに変わっています。

Rails3でRspecを使う方法

Haml、Sassを使えるようになったので今度はRspecを使えるようにします。

インストール

Rails3ではデフォルトでTest::Unit用のディレクトリやファイルが生成されるのですが、これは不要なのでプロジェクト作成時にTest::Unitを使わないようにします。

$ rails new rails3_rspec -T #-TでSkip Test::Unit

次にGemfileに必要なgemを記述します。
※同時にHaml、Sassを使う方法については以前の記事を参照のこと
Rails3とRuby1.9.2でHamlを使う方法
Rails3でSassを使う方法

gem 'haml-rails'

group :development, :test do
  gem 'rspec-rails', '>= 2.0.0.beta.20'
  gem 'autotest'
  gem 'webrat'
end

Gemfileができたらインストール

$ bundle install vendor/bundle
$ rails generate rspec:install

動作確認

動作確認のためにscaffoldで一通りのファイルを生成してテストを実行

$ rails generate scaffold sample
$ rake db:migrate
$ rake db:test:prepare #テスト用DBを作成
$ rake spec
...............**............
(省略)
29 examples, 0 failures, 2 pending

こんな感じでRspecが使えるようになります。
また、autotestを使うとファイルに変更があったときに自動でテストが走るようになります。

$ bundle exec autotest

Rails3でSassを使う方法

せっかくHamlを使えるようにしたのでSassも使えるようにしたいところ。
とはいってもSassはHamlに同梱されているので前回の記事の手順でインストールまでは既に済んでいます。

設定

config/initializers/sass_config.rbを作成して以下の記述を追加。

#デフォルトのスタイルシートパスを削除
Sass::Plugin.remove_template_location("./public/stylesheets/sass")
#app/stylesheetsをSass用のディレクトリに設定
Sass::Plugin.add_template_location("./app/stylesheets")
#最小サイズのCSSに変換する
Sass::Plugin.options[:style] = :compressed

デフォルトのパスでも問題ないのですが、Sassは.sassファイルから.cssファイルへコンパイルする仕組みのためソースファイルと生成されたCSSの場所を分離しておいたほうがスッキリすると思います。
またSassの変換結果のスタイルは以下のドキュメントに書かれていますので好きなものを設定してください。
http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#output_style

動作確認

Sass用のディレクトリとファイルを作成

$ mkdir app/stylesheets
$ vim app/stylesheets/sample.sass

以下の記述をsample.sassに追加

h1
  :font
    :size 200%
    :weight bold
  :border 16px solid #DD66CC
  :width 400px
  :padding 5px 
p
  :font-weight bold
  :border 16px solid #6666DD
  :width 400px
  :padding 5px 
$ rails server
$ open http://localhost:3000/sample/index

こんな画面が表示されたら動作確認OK。public/stylesheets/sample.cssが生成されているはず。
f:id:y_310:20100906005347p:image

Rails3とRuby1.9.2でHamlを使う方法

会社でRails3とHamlを使っているんですが、既に他の方が環境を作った上でコードを書いていたので、勉強のために一から環境構築をやってみました。

Rails環境構築

$ rvm 1.9.2
$ gem install rails
$ rails new rails3_haml
$ cd rails3_haml
$ bundle install vendor/bundle #必要なgemをvender/bundleに一括インストール
$ rails server

これでブラウザからhttp://localhost:3000にアクセスして動作していればひとまず最低限のRailsの環境構築はOK.

Hamlインストール

Gemfileにhaml-rails*1を追加。

gem 'haml-rails'

haml-railsはRails3にHaml Generatorを追加するためのプラグインです。
元々はrails3-generatorsだったものがHaml部分だけ分離したものらしいです。

もう一度bundle install

$ bundle install

これで準備は完了。
config/application.rbにg.template_engine :hamlのような記述を追加する必要はありません。

あとは試しにcontrollerをgenerateして動作確認してみましょう。

$ rails g controller sample index
      create  app/controllers/sample_controller.rb
       route  get "sample/index"
      invoke  haml     #template_engineがhamlに変わっている
       exist    app/views/sample
      create    app/views/sample/index.html.haml
      invoke  test_unit
      create    test/functional/sample_controller_test.rb
      invoke  helper
   identical    app/helpers/sample_helper.rb
      invoke    test_unit
   identical      test/unit/helpers/sample_helper_test.rb
$ rails server

これでhttp://localhost:3000/sample/indexにアクセスしてこんな画面が表示されればhamlが動作しています。
f:id:y_310:20100831005131p:image

*1:haml-railsを入れずにgem 'haml'だけでもHamlを使うことはできますが、rails gでファイルをgenerateする際にerbファイルが生成されてしまいます。これを手動で書き換えるか自分でファイルを作成する場合はhaml-railsは必要ありません。

やってみた

makeplex salon:あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定 (1/2) - ITmedia エンタープライズ
3時間でできなければ優秀でないことが証明されてしまうことで一部で話題になっている問題をやってみた。
結果は...



“ほぼ確実に”優秀ではない!orz

とりあえずコード晒しとく。Rubyで書いたのにやけに長い。
真面目にアルゴリズムの勉強しないとだめだなぁ。

Kotsu = 1
Shuntsu = 2
Any = 3
class Array
    def Array.deepcopy(arr)
        Marshal.load(Marshal.dump(arr))
    end

    def top
        self[self.length - 1]
    end

    def car
        if self.length > 0
            self[0]
        else
            nil
        end
    end

    def cdr
        new_a = Array.deepcopy(self)
        new_a.shift
        new_a
    end

    def rest(i)
        new_a = Array.deepcopy(self)
        new_a.delete_at(i)
        new_a
    end
end

class Machi
    def initialize
        @machi = nil
        @atama = nil
        @triple = []
    end
    attr_accessor :machi, :atama, :triple

    def eql?(m)
        self.hash == m.hash
    end

    def hash
        str = @triple.sort.join
        str += @atama.join if @atama
        str += @machi.join if @machi
        str.hash
    end

    def print
        @triple.sort!
        @triple.each{|t|
            STDOUT.print "(#{t.join})"
        }
        STDOUT.print "(#{@atama.join})" if @atama && @atama.length > 0
        STDOUT.print "[#{@machi.join}]" if @machi
        STDOUT.print "\n"
    end

    def deepcopy
        new_machi = Machi.new
        new_machi.machi = Array.deepcopy(self.machi)
        new_machi.atama = Array.deepcopy(self.atama)
        new_machi.triple = Array.deepcopy(self.triple)
        new_machi
    end
end

def check_four_tiles(n)
    $four_tiles.each{|t| return true if t == n}
    return false
end

def search_same(temp, rest)
    return {} if temp.length == 0
    v = temp[0]
    for i in 0...rest.length
        if v == rest[i]
            return {v => rest.rest(i)}
        end
    end
    return {}
end

def search_forward_backword(temp, rest)
    return {} if temp.length == 0
    if temp.length == 2
        return {} if temp[1] - temp[0] != 1
    end
    forward = temp.first - 1
    backward = temp.last + 1
    result = {}
    for i in 0...rest.length
        if rest[i] == forward || rest[i] == backward
            if !result.key?(rest[i])
                result[rest[i]] = rest.rest(i)
            end
        end
    end
    return result
end

def search_aida_machi(temp, rest)
    return {} if temp.length != 1
    forward = temp.first - 2
    backward = temp.last + 2
    result = {}
    for i in 0...rest.length
        if rest[i] == forward || rest[i] == backward
            if !result.key?(rest[i])
                result[rest[i]] = rest.rest(i)
            end
        end
    end
    return result
end

def get_machi(shuntsu2)
    return [] if shuntsu2.length != 2
    case shuntsu2[1] - shuntsu2[0]
    when 1
        ret = []
        ret << shuntsu2[0] - 1 if shuntsu2[0] > 1
        ret << shuntsu2[1] + 1 if shuntsu2[1] < 9
        return ret
    when 2
        return [shuntsu2[1] - 1]
    else
        return []
    end
end

def search(type, rest, temp, result)
    if rest.length == 0 && temp.length == 0
        $final_result << result
        return
    end
    case type
    when Kotsu
        search_same(temp, rest).each{|num, new_rest|
            new_result = result.deepcopy
            new_result.triple << (temp + [num])
            search(Any, new_rest, [], new_result)
        }
    when Shuntsu
        search_forward_backword(temp, rest).each{|num, new_rest|
            new_result = result.deepcopy
            new_result.triple << (temp + [num]).sort
            search(Any, new_rest, [], new_result)
        }
    when Any
        if temp.length == 0
            search(Any, rest.cdr, temp + [rest.car], result.deepcopy)
            if !result.atama && !result.machi && !check_four_tiles(rest.car) #一個待ちパターン
                new_result = result.deepcopy
                new_result.machi = [rest.car]
                new_result.atama = []
                search(Any, rest.cdr, [], new_result)
            end
        elsif temp.length == 1
            search_same(temp, rest).each{|num, new_rest|
                search(Kotsu, new_rest, temp + [num], result.deepcopy)
                if !result.atama #頭パターン
                    new_result = result.deepcopy
                    new_result.atama = temp + [num]
                    search(Any, new_rest, [], new_result)
                end
                if !result.machi && !check_four_tiles(num) #刻子待ちパターン
                    new_result = result.deepcopy
                    new_result.machi = temp + [num]
                    search(Any, new_rest, [], new_result)
                end
            }
            search_forward_backword(temp, rest).each{|num, new_rest|
                search(Shuntsu, new_rest, (temp + [num]).sort, result.deepcopy)
                exist_machi = false
                get_machi((temp + [num]).sort) .map{|m| !check_four_tiles(m)} .each{|m| exist_machi |= m}
                if !result.machi && exist_machi #順子待ちパターン
                    new_result = result.deepcopy
                    new_result.machi = (temp + [num]).sort
                    search(Any, new_rest, [], new_result)
                end
            }
            if !result.machi
                search_aida_machi(temp, rest).each{|num, new_rest|
                    exist_machi = false
                    get_machi((temp + [num]).sort) .map{|m| !check_four_tiles(m)} .each{|m| exist_machi |= m}
                    if exist_machi
                        new_result = result.deepcopy
                        new_result.machi = (temp + [num]).sort
                        search(Any, new_rest, [], new_result)
                    end
                }
            end
        end
    end
end

def check_number_of_every_tile(tiles)
    arr = []
    ret = []
    arr.fill(0, 1..9)
    tiles.each{|t| arr[t.to_i] += 1}
    for i in 1..9
        ret << i if arr[i] == 4
    end
    ret
end

$final_result = []
$four_tiles = []
input = [
    "1112224588899",
    "1122335556799",
    "1112223335559",
    "1223344888999",
    "1112345678999",
    "1113335558888",
    "1222233456788",
    "1223567789789"
]

input.each{|i|
    $final_result.clear
    $four_tiles.clear
    puts i
    nums = i.split(//).map{|n| n.to_i}.sort
    $four_tiles = check_number_of_every_tile(nums)
    search(Any, nums, [], Machi.new)
    $final_result.uniq.each{|r| r.print}
    puts
}

id<Protocol>と書くときの@protocolの宣言の方法

delegateをメンバに持つようなクラスを作るとき、delegateの型をidじゃなく特定のProtocolが実装されていることを指定したい時がある。そういう時はid delegate;と書けばいいんだけど、自作のProtocolだとrespondsToSelectorとかのNSObjectで定義されているメソッドが無い、と警告が出る。
調べてみたところ以下のページに解説があった。
id<Protocol>, Retain/Release and Protocol Inheritance | Mobile Orchard
Protocolの宣言時にNSObject Protocolを継承すればいいらしい。
つまり

@protocol MyProtocol <NSObject>
- (void)hoge;
@end

と書けば警告が出なくなる。

iPhoneで画像処理の高速化実験

iPhoneでOpenCVが使えることを知ったので、ついでに画像処理の高速化について今までもやもやしていたことをはっきりさせようと思い立ち実験してみた。

題材は画像のモザイク化

f:id:y_310:20100131002511p:image:left
f:id:y_310:20100131002831p:image

githubにあったこちら
niw/iphone_opencv_test · GitHub
のサンプルコードからforkさせてもらってモザイク化処理を追加した。
y310/iphone_opencv_test · GitHub
コンパイルオプションはXcodeのデフォルトで-Os(最も高速で最小)、iPhoneOS3.1.2のReleaseビルドで実験した。

Step1 とりあえず何も考えずに実装

モザイク処理メソッドから抜粋。
元画像のmSize*mSize領域のRGB平均値を計算し、出力先画像の同領域をその平均値で埋める、という処理。

int mSize = [mosaicSize intValue];
IplImage *img = [self CreateIplImageFromUIImage:imageView.image];
IplImage *dst = cvCreateImage(cvGetSize(img), img->depth, img->nChannels);
unsigned int r, g, b;
int p, pp, c;

for (int i = 0; i < img->height; i += mSize) {
    for (int j = 0; j < img->width; j += mSize) {
        r = b = g = 0;
        p = (i * img->width + j) * 3;
        c = 0;
        for (int k = 0; k < mSize; k++) {
            if (i + k < img->height) {
                for (int l = 0; l < mSize; l++) {
                    if (j + l < img->width) {
                        pp = p + (k * img->width + l) * 3;
                        b += (unsigned char)img->imageData[pp];
                        g += (unsigned char)img->imageData[pp + 1];
                        r += (unsigned char)img->imageData[pp + 2];
                        c++;
                    }
                }
            }
        }
        r /= c;
        g /= c;
        b /= c;
        
        for (int k = 0; k < mSize; k++) {
            if (i + k < img->height) {
                for (int l = 0; l < mSize; l++) {
                    if (j + l < img->width) {
                        pp = p + (k * img->width + l) * 3;
                        dst->imageData[pp] = (char)r;
                        dst->imageData[pp + 1] = (char)g;
                        dst->imageData[pp + 2] = (char)b;
                    }
                }
            }
        }
    }
}
cvReleaseImage(&img);
imageView.image = [self UIImageFromIplImage:dst];
cvReleaseImage(&dst);

この処理でmSizeを2,4,8,16,32,64,128で順番に実行した時の結果(5回の平均、単位は秒)

Step1
2 0.2202906
4 0.1995558
8 0.1747878
16 0.1725956
32 0.1691068
64 0.166092
128 0.1662554
average: 0.1812404

これを基準に高速化してみる。

Step2 img->widthをwidthに変換(heightも)

forループなどで使っているimg->widthが毎回imgからwidthをたどっていて効率が悪いのではないかと考え、ループの前で値を保存する用に変更。

int width = img->width;
int height = img->height;

for (int i = 0; i < height; i += mSize) {
    for (int j = 0; j < width; j += mSize) {
        r = b = g = 0;
        p = (i * width + j) * 3;
        c = 0;
        for (int k = 0; k < mSize; k++) {
            if (i + k < height) {
                for (int l = 0; l < mSize; l++) {
                    if (j + l < width) {
                        pp = p + (k * width + l) * 3;
                        b += (unsigned char)img->imageData[pp];
                        g += (unsigned char)img->imageData[pp + 1];
                        r += (unsigned char)img->imageData[pp + 2];
                        c++;
                    }
                }
            }
        }
        r /= c;
        g /= c;
        b /= c;
        
        for (int k = 0; k < mSize; k++) {
            if (i + k < height) {
                for (int l = 0; l < mSize; l++) {
                    if (j + l < width) {
                        pp = p + (k * width + l) * 3;
                        dst->imageData[pp] = (char)r;
                        dst->imageData[pp + 1] = (char)g;
                        dst->imageData[pp + 2] = (char)b;
                    }
                }
            }
        }
    }
}

実行結果

Step1 Step2
2 0.2202906 0.2058164
4 0.1995558 0.1892952
8 0.1747878 0.1648164
16 0.1725956 0.1648778
32 0.1691068 0.163123
64 0.166092 0.1677026
128 0.1662554 0.1650716
average: 0.1812404 0.174386

ちょっと早くなった。

Step3 ifの削減

画像サイズがmSizeで割り切れない時のためにif (i + k < height)という形でループ内でifの判定を行っていた部分を、事前にkMax,lMaxにループの最大値を保存することで削除した。

int kMax, lMax;
for (int i = 0; i < height; i += mSize) {
    for (int j = 0; j < width; j += mSize) {
        r = b = g = 0;
        p = (i * width + j) * 3;
        c = 0;
        kMax = i + mSize < height ? mSize : height - i;
        lMax = j + mSize < width  ? mSize : width - j;
        for (int k = 0; k < kMax; k++) {
            for (int l = 0; l < lMax; l++) {
                pp = p + (k * width + l) * 3;
                b += (unsigned char)img->imageData[pp];
                g += (unsigned char)img->imageData[pp + 1];
                r += (unsigned char)img->imageData[pp + 2];
                c++;
            }
        }
        r /= c;
        g /= c;
        b /= c;
        
        for (int k = 0; k < kMax; k++) {
            for (int l = 0; l < lMax; l++) {
                pp = p + (k * width + l) * 3;
                dst->imageData[pp] = (char)r;
                dst->imageData[pp + 1] = (char)g;
                dst->imageData[pp + 2] = (char)b;
            }
        }
    }
}

実行結果

Step1 Step2 Step3
2 0.2202906 0.2058164 0.1998938
4 0.1995558 0.1892952 0.1865738
8 0.1747878 0.1648164 0.1517498
16 0.1725956 0.1648778 0.1577388
32 0.1691068 0.163123 0.1486748
64 0.166092 0.1677026 0.1521254
128 0.1662554 0.1650716 0.1549348
average: 0.1812404 0.174386 0.1645274

Step2から約0.01秒早くなった。

Step4 img->imageDataも削減&c++をc=kMax*lMaxに

width,heightと同じ考え方でimg->imageDataもimgImageDataに変換。
あと対象領域の平均値計算に使っていたcを毎回ループ内でカウントするのをやめてkMax*lMaxで計算。(最初に気付けと思ったがStep1の段階ではifの判定で数が変わるため事前には計算できなかった)

char *imgImageData = img->imageData;
char *dstImageData = dst->imageData;
for (int i = 0; i < height; i += mSize) {
    for (int j = 0; j < width; j += mSize) {
        r = b = g = 0;
        p = (i * width + j) * 3;
        kMax = i + mSize < height ? mSize : height - i;
        lMax = j + mSize < width  ? mSize : width - j;
        c = kMax * lMax;
        for (int k = 0; k < kMax; k++) {
            for (int l = 0; l < lMax; l++) {
                pp = p + (k * width + l) * 3;
                b += (unsigned char)imgImageData[pp];
                g += (unsigned char)imgImageData[pp + 1];
                r += (unsigned char)imgImageData[pp + 2];
            }
        }
        r /= c;
        g /= c;
        b /= c;
        
        for (int k = 0; k < kMax; k++) {
            for (int l = 0; l < lMax; l++) {
                pp = p + (k * width + l) * 3;
                dstImageData[pp] = (char)r;
                dstImageData[pp + 1] = (char)g;
                dstImageData[pp + 2] = (char)b;
            }
        }
    }
}

実行結果

Step1 Step2 Step3 Step4
2 0.2202906 0.2058164 0.1998938 0.1796986
4 0.1995558 0.1892952 0.1865738 0.1683434
8 0.1747878 0.1648164 0.1517498 0.148594
16 0.1725956 0.1648778 0.1577388 0.1463726
32 0.1691068 0.163123 0.1486748 0.1627986
64 0.166092 0.1677026 0.1521254 0.1537092
128 0.1662554 0.1650716 0.1549348 0.151777
average: 0.1812404 0.174386 0.1645274 0.1587564

ここまでで初期状態から約0.023秒速くなった。

Step5 ppの計算回数を削減

k-lループの内側で毎回現在の注目ピクセル座標ppを計算していたが、kループの内側で計算すればlループ内はインクリメントだけでいける。

for (int i = 0; i < height; i += mSize) {
    for (int j = 0; j < width; j += mSize) {
        r = b = g = 0;
        p = (i * width + j) * 3;
        kMax = i + mSize < height ? mSize : height - i;
        lMax = j + mSize < width  ? mSize : width - j;
        c = kMax * lMax;
        for (int k = 0; k < kMax; k++) {
            pp = p + (k * width * 3);
            for (int l = 0; l < lMax; l++) {
                b += (unsigned char)imgImageData[pp++];
                g += (unsigned char)imgImageData[pp++];
                r += (unsigned char)imgImageData[pp++];
            }
        }
        r /= c;
        g /= c;
        b /= c;
        
        for (int k = 0; k < kMax; k++) {
            pp = p + (k * width * 3);
            for (int l = 0; l < lMax; l++) {
                dstImageData[pp++] = (char)r;
                dstImageData[pp++] = (char)g;
                dstImageData[pp++] = (char)b;
            }
        }
    }
}

実行結果

Step1 Step2 Step3 Step4 Step5
2 0.2202906 0.2058164 0.1998938 0.1796986 0.1845956
4 0.1995558 0.1892952 0.1865738 0.1683434 0.1641452
8 0.1747878 0.1648164 0.1517498 0.148594 0.1551546
16 0.1725956 0.1648778 0.1577388 0.1463726 0.1454828
32 0.1691068 0.163123 0.1486748 0.1627986 0.147119
64 0.166092 0.1677026 0.1521254 0.1537092 0.1495658
128 0.1662554 0.1650716 0.1549348 0.151777 0.1533584
average: 0.1812404 0.174386 0.1645274 0.1587564 0.15706

一応速くなった…かな?ちょっと誤差かもしれない微妙な差。

Step6 掛け算を削減

掛け算より足し算の方が速いと聞いたことがあったのでppの計算から掛け算をなくしてみる。
Step5でppは繰り返しの度にwidth*3を増やす処理と同値だったので毎回width*3を足すようにする。

for (int i = 0; i < height; i += mSize) {
    for (int j = 0; j < width; j += mSize) {
        r = b = g = 0;
        p = (i * width + j) * 3;
        kMax = i + mSize < height ? mSize : height - i;
        lMax = j + mSize < width  ? mSize : width - j;
        c = kMax * lMax;
        pp = offset = p;
        for (int k = 0; k < kMax; k++) {	
            for (int l = 0; l < lMax; l++) {
                b += (unsigned char)imgImageData[pp++];
                g += (unsigned char)imgImageData[pp++];
                r += (unsigned char)imgImageData[pp++];
            }
            offset += stride;
            pp = offset;
        }
        r /= c;
        g /= c;
        b /= c;
        pp = offset = p;
        for (int k = 0; k < kMax; k++) {
            for (int l = 0; l < lMax; l++) {
                dstImageData[pp++] = (char)r;
                dstImageData[pp++] = (char)g;
                dstImageData[pp++] = (char)b;
            }
            offset += stride;
            pp = offset;
        }
    }
}

実行結果

Step1 Step2 Step3 Step4 Step5 Step6
2 0.2202906 0.2058164 0.1998938 0.1796986 0.1845956 0.186088
4 0.1995558 0.1892952 0.1865738 0.1683434 0.1641452 0.1658398
8 0.1747878 0.1648164 0.1517498 0.148594 0.1551546 0.1556316
16 0.1725956 0.1648778 0.1577388 0.1463726 0.1454828 0.14568
32 0.1691068 0.163123 0.1486748 0.1627986 0.147119 0.1497236
64 0.166092 0.1677026 0.1521254 0.1537092 0.1495658 0.1495576
128 0.1662554 0.1650716 0.1549348 0.151777 0.1533584 0.1559454
average: 0.1812404 0.174386 0.1645274 0.1587564 0.15706 0.1583524

遅くなった…。1行の掛け算だったのが足し算と代入の2行に別れたせいだろうか。
理由は分からないがとりあえずあまり効果はないようだ。

Step7 構造体を使って代入を減らす

出力先画像への代入処理が同じ値をRGBの3回分毎回やるのは非効率な気がするのでunsigned charのRGB値を保持する構造体を作って、1回で構造体まるごと代入するようにしてみた。
正直この方法はchar*のimageDataを無理矢理struct Rgb*にキャストしてたりするので危険だと思う。
構造体もきっちりunsigned char*3つ分になる保証はないはず。

struct Rgb *dstImageData = (struct Rgb*)dst->imageData;
for (int i = 0; i < height; i += mSize) {
    kMax = i + mSize < height ? mSize : height - i;
    for (int j = 0; j < width; j += mSize) {
        r = b = g = 0;
        p = (i * width + j) * 3;
        lMax = j + mSize < width  ? mSize : width - j;
        c = kMax * lMax;
        pp = offset = p;
        for (int k = 0; k < kMax; k++) {	
            for (int l = 0; l < lMax; l++) {
                b += (unsigned char)imgImageData[pp++];
                g += (unsigned char)imgImageData[pp++];
                r += (unsigned char)imgImageData[pp++];
            }
            offset += stride;
            pp = offset;
        }
        rgb.r = r / c;
        rgb.g = g / c;
        rgb.b = b / c;
        pp = offset = i * width + j;
        for (int k = 0; k < kMax; k++) {
            for (int l = 0; l < lMax; l++) {
                dstImageData[pp++] = rgb;
            }
            offset += width;
            pp = offset;
        }
    }
}

実行結果

Step1 Step2 Step3 Step4 Step5 Step6 Step7
2 0.2202906 0.2058164 0.1998938 0.1796986 0.1845956 0.186088 0.2409104
4 0.1995558 0.1892952 0.1865738 0.1683434 0.1641452 0.1658398 0.203338
8 0.1747878 0.1648164 0.1517498 0.148594 0.1551546 0.1556316 0.184218
16 0.1725956 0.1648778 0.1577388 0.1463726 0.1454828 0.14568 0.1747662
32 0.1691068 0.163123 0.1486748 0.1627986 0.147119 0.1497236 0.1759188
64 0.166092 0.1677026 0.1521254 0.1537092 0.1495658 0.1495576 0.1728034
128 0.1662554 0.1650716 0.1549348 0.151777 0.1533584 0.1559454 0.1736168
average: 0.1812404 0.174386 0.1645274 0.1587564 0.15706 0.1583524 0.1893676

さらに遅くなった…。というかStep1より遅い。なんでこんなに遅くなるんだろうか。
そろそろアセンブラレベルでちゃんと見ないといけない気がしてきた。

とりあえずまとめ

今までなんとなくこう書いた方が速くなるかなぁと思っていたことが、実際効果があったり逆効果だったりすることがわかった。
結局この辺どれが本当に正しいかはマシン語レベルで見ないとわからなそう。まだまだコンパイラの最適化だけに頼ってちゃいけないんですね。

追記

通りすがりさんからアイディアをいただいたので組み込んでみた。

void mosaic(char *imgImageData, char *dstImageData, int width, int sX, int sY, int w, int h, int wmSize, int hmSize, int stride) {
	int dp = width * 3;
	int dpp = wmSize * 3;
	unsigned int r, g, b;
	int p, pp, c;
	c = wmSize * hmSize;
	
	for (int y = sY; y < h; y++) {
		p = y * dp * hmSize;
		for (int x = sX; x < w; x++, p += dpp) {
			r = b = g = 0;
			pp = p;
			for (int yy = 0; yy < hmSize; yy++) {
				for (int xx = 0; xx < wmSize; xx++) {
					b += (unsigned char)imgImageData[pp++];
					g += (unsigned char)imgImageData[pp++];
					r += (unsigned char)imgImageData[pp++];
				}
				pp += stride;
			}
			r /= c;
			g /= c;
			b /= c;
			
			pp = p;
			for (int yy = 0; yy < hmSize; yy++) {
				for (int xx = 0; xx < wmSize; xx++) {
					dstImageData[pp++] = (char)r;
					dstImageData[pp++] = (char)g;
					dstImageData[pp++] = (char)b;
				}
				pp += stride;
			}
		}
	}
}

{
...
    int wBlockCnt = width / mSize;
    int hBlockCnt = height / mSize;

    /* 割り切れる領域 */
    mosaic(imgImageData, dstImageData, width, 0, 0, wBlockCnt, hBlockCnt, mSize, mSize, (width - mSize) * 3);

    /* 割り切れない領域 */
    if ( width % mSize > 0) {
        mosaic(imgImageData, dstImageData, width, wBlockCnt, 0, wBlockCnt + 1, hBlockCnt, width % mSize, mSize, (width - (width % mSize)) * 3);
    }
    if ( height % mSize > 0) {
        mosaic(imgImageData, dstImageData, width, 0, hBlockCnt, wBlockCnt, hBlockCnt + 1, mSize, height % mSize, (width - mSize) * 3);
    }
    if ( width % mSize > 0 && height % mSize > 0) {
        mosaic(imgImageData, dstImageData, width, wBlockCnt, hBlockCnt, wBlockCnt + 1, hBlockCnt + 1, width % mSize, height % mSize, (width - (width % mSize)) * 3);
    }
...
}
Step1 Step2 Step3 Step4 Step5 Step6 Step7 Step8
2 0.2202906 0.2058164 0.1998938 0.1796986 0.1845956 0.186088 0.2409104 0.1631222
4 0.1995558 0.1892952 0.1865738 0.1683434 0.1641452 0.1658398 0.203338 0.15106
8 0.1747878 0.1648164 0.1517498 0.148594 0.1551546 0.1556316 0.184218 0.1435718
16 0.1725956 0.1648778 0.1577388 0.1463726 0.1454828 0.14568 0.1747662 0.142803
32 0.1691068 0.163123 0.1486748 0.1627986 0.147119 0.1497236 0.1759188 0.1379126
64 0.166092 0.1677026 0.1521254 0.1537092 0.1495658 0.1495576 0.1728034 0.13246
128 0.1662554 0.1650716 0.1549348 0.151777 0.1533584 0.1559454 0.1736168 0.1457272
average: 0.1812404 0.174386 0.1645274 0.1587564 0.15706 0.1583524 0.1893676 0.1452366

最速記録更新!余計なif文がなくなったのが大きいのかな。

さらに追記

さらに改良コードを貼っていただいたので取り込んでみました。
が、Step1より遅い結果に...正確な原因はわかりませんが剰余演算とif文がループ内に増えた分と配列の要素参照が増えたところあたりがアルゴリズムの改善効果以上に効いてるのでしょうか。

Step1 Step2 Step3 Step4 Step5 Step6 Step7 Step8 Step9
2 0.2202906 0.2058164 0.1998938 0.1796986 0.1845956 0.186088 0.2409104 0.1631222 0.297052
4 0.1995558 0.1892952 0.1865738 0.1683434 0.1641452 0.1658398 0.203338 0.15106 0.26692
8 0.1747878 0.1648164 0.1517498 0.148594 0.1551546 0.1556316 0.184218 0.1435718 0.2529658
16 0.1725956 0.1648778 0.1577388 0.1463726 0.1454828 0.14568 0.1747662 0.142803 0.2481548
32 0.1691068 0.163123 0.1486748 0.1627986 0.147119 0.1497236 0.1759188 0.1379126 0.2526098
64 0.166092 0.1677026 0.1521254 0.1537092 0.1495658 0.1495576 0.1728034 0.13246 0.248334
128 0.1662554 0.1650716 0.1549348 0.151777 0.1533584 0.1559454 0.1736168 0.1457272 0.2471866
average: 0.1812404 0.174386 0.1645274 0.1587564 0.15706 0.1583524 0.1893676 0.1452366 0.2590316