athome-developer’s blog

不動産情報サービスのアットホームの開発者が発信するブログ

ASP.NET Coreのパフォーマンス検証

こんにちは! 情報システム部の高野です。
前回、予告した通りASP.NET Coreのパフォーマンス検証をしました。
dblog.athome.co.jp

検証環境
  1. IISにAspNetCoreModuleを使ってホスト
  2. IISからKestrelへリバースプロキシ
  3. WindowsのNginxからKestrelへリバースプロキシ
  4. CentOSのNginxからKestrelへリバースプロキシ

の4種類です。

OSは、
Windows 2016 server
CentOS7.2
です。

.NET Coreのバージョンは1.0.1です。

チューニングは基本的になにもせずデフォルト値でなにが速いのかを検証します。
リバースプロキシしている3つは、Webサーバとアプリケーションは同サーバで実行しています。
CentOSの方は、Kestrelもサービス化して常駐するようにしました。
Windowsのリバースプロキシしている方は、サービス化がちょっと面倒だったので
コンソールから起動している状態です。

負荷

tsungというツールで1秒ごとに100ユーザずつ増やしながら
1ユーザが10リクエストするようにして1分間流します。

検証項目

tsungの結果とASP.NET Core、各Webサーバのログから出しています。
ASP.NET Coreのログは、NLogを使い
Asyncにしてバッファリングもしてログの書き出しがパフォーマンスになるべく影響のないようにしてあります。

検証するプログラムは、下記のただ文字列を返すだけのものです。

public class PerfTestController : Controller
{
    public string Greeting()
    {
        return "Hello World!";
    }
}
結果

各ログの平均値

tsung Webログ APログ
01.IISホスト 1.81 ms 1ms 0.14ms
02.IISリバプロ 5,310ms 5,260ms 1.54ms
03.Win+Nginx 530ms 832ms 1.63ms
04.CentOS+Nginx 1.90ms 0.74ms 0.09ms

Windowsでのリバースプロキシが間違ってるんじゃないってくらい遅いです。
特にWindowsのNginxの方は、Webサーバのログの方がtsungより大きくなっているので
明らかにおかしいのですが、何回やってもこのような結果になりました。
Webサーバのログを見てもIISにホストした場合やCentOS+Nginxに比べて
1秒間に半分ほどしかリクエストを捌けてません。

なんで同じようにリバースプロキシしているのにWindowsの方が圧倒的に遅いのか
納得いかなかったので
CentOSの方もサービス化せずにコンソールで起動して検証をしてみました。

tsung Webログ APログ
CentOS+Nginx 11,1640ms 5,572ms 757ms

やっぱりコンソールで起動するとかなり遅くなりました。*1

まとめ

この結果だけを見るとASP.NET Coreを動かすのは
ちゃんとサービス化して動かした方が良いってことですね。
まあ上でも書いた通りWindowsでサービス化するのはちょっと面倒なので
IISにAspNetCoreModuleを入れてホストするか
Linuxでリバースプロキシするのが良さそうです。

今回は、文字列を返すだけのAPIで検証してみたのですが
今度は普通にありそうなユースケースで検証してみたいと思います。

*1:上記は、dotnet run -c Release で起動した結果ですがpublishしてから dotnet sample.dllで起動しても同様の結果になりました。

CentOS7でASP.NET Coreを動かす

こんにちは、情報システム部の高野です。
.Net CoreをLinuxで動かす事例は、Ubuntuが多いのですが
弊社は基本的にRed Hat Enterprise Linuxを利用しているので
検証も同様にRed Hat Enterprise Linuxで行うべく
CentOS上にASP.NET Coreの環境を構築したのですが
結構、はまったので手順を残しておきます。*1

環境

CentOS7.2
dotnet Core1.0.1
nginx 1.10.2

dotnetのセットアップ

.NET - Powerful Open Source Development
ここに書いてある通りにやれば動かすことはできました。
Webのプロジェクトを作成してdotnet runをすればWebサイトも普通に動くはずです。
※この時点ではkestrelをターミナルで実行して動かしてます。

nginxのセットアップ

軽く動作を確認するだけならkestrelだけでも問題ないのですが
ちゃんと運用する際には、nginxなどのWebサーバが必要になります。

yumでnginxをインストールします。

> sudo yum install http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
> sudo yum install --enablerepo=nginx nginx

設定ファイルを修正します。
/etc/nginx/conf.d/default.confをテキストエディタで開き下記のように修正します。
※locationの部分だけです

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/log/host.access.log  main;

    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
   ・・・
}


SELinuxを停止します。
(本番環境では停止せずにちゃんと設定してください)

> sudo setenforce 0

※再起動後も停止したままにするなら /etc/selinux/config を書き換えます。

nginxを起動します

> sudo systemctl start nginx.service
> sudo systemctl enable nginx.service

2行目は、再起動時もnginxが起動するようにするためのものなので
必要に応じて実行してください。

dotnet runでkestrelを実行します。
curlでもブラウザでもいいので80番ポートにアクセスします。
作成したWebサイトが表示されればリバースプロキシの設定は完了です。

kestrelをバックグラウンドで実行する

このままだとターミナルを閉じるとWebサイトが利用できなくなってしまうので
バックグラウンドでkestrelを実行するようにします。

Publish to a Linux Production Environment
当初はこちらにあるようにSuperVisorを使って常駐化しようとしたのですが
CentOS7では、どうやらSuperVisorなどをインストールしなくても常駐化が簡単にできるようなので
そちらのやり方で実行しました。

/etc/systemd/systemにkestrel.serviceというファイルを作成します。
(ファイル名は、任意なのでkestrelでなくても良いです)

[Unit]
Description=kestrel server
After=syslog.target network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/dotnet /home/hoge/sampleApp/SampleApp.dll
WorkingDirectory=/home/hoge/sampleApp
KillMode=process
Restart=always
User=hoge
Group=hoge

[Install]
WantedBy=multi-user.target

ExecStartとWorkingDirectoryは任意のパスです。
dotnet publishでデプロイしたディレクトリを指定します。*2

あとは、nginx同様起動します

> sudo systemctl start kestrel.service
> sudo systemctl enable kestrel.service


これでターミナルを閉じても再起動後でもWebサイトにアクセスできます。
分かってしまえば結構簡単ですね

*1:現在、AzureでもRHELは選択できるのですが、つい癖でCentOSを選択してしまいました

*2:通常のテンプレートだとdotnet publishをするときにnpm等が必要になります

.NET Coreのパフォーマンスが気になる件

こんにちは! 情報システム部の高野です。
.NET Coreは、WindowsLinuxMacで動く新しい.NETの環境です。
気になっているのは、Webアプリケーションを動かすならWindowsLinuxどちらが速いのかってことです。
折角、マルチプラットフォームに対応したのでより良い環境で動かしたいですからね
(まあ速いってのは1つの要素に過ぎないですけどね)

今回は、序章としてコンソールアプリケーションでパフォーマンスの確認をしてみたいと思います。

環境

Azure上にWindows2016 ServerとCentOS7.2を構築。
どちらもDS2_V2 Standard(CPU:2コア・メモリ:7GB)でSSDを選択。
※特に設定などは弄らずに素のままで検証してます。

.NET Coreのバージョンは1.0.1です。

検証用プログラム

適当に負荷が、かかれば良いのでかなり適当ですが

public static void Main(string[] args)
{
    var a = Enumerable.Range(1, 10000);

    for(var i = 1; i <= 10000; i++)
    {
        var b = a.Where(x => x % i == 0).ToArray();
    }
}
検証結果

上記プログラムを10回実行した平均です。

Windows2016:1,204.4ms
CentOS7.2:1,125.5ms

まあ誤差の範囲ですね
でも何回かやってもCentOSの方が速かったです。
ちなみに自分のSurface Pro3で実行したら3秒以上かかりました。
(自分のPCの遅さが浮き彫りに!)

上記は、dotnet runコマンドで実行した結果なのですが、
これだとdebugモードで実行しているってことに気付いたので
dotnet run -c Release で実行してみた結果、こうなりました。

Windows2016:997.7ms
CentOS7.2:945.9ms

ちゃんと速くなってる!(当たり前ですね)


次回は、本題のASP.NET Coreの検証を実施してみたいと思います。

LodashとLINQの対応表にRubyを足してみる(そしてSymbol#to_proc)

情報システム部の山田です。こんにちは。
先日当社の高野さんがこんな記事を書いてくれたので乗っかってみよう!というわけで一覧表に、Rubyだとこれだよっ!を追加してみました。

Rubyだとこのメソッド

LINQ Lodash Ruby
射影 Select map map, collect
フィルター Where filter select, find_all
昇順ソート OrderBy orderBy sort, sort_by
降順ソート OrderByDescending orderBy sort, sort_by
グループ化 GroupBy groupBy group_by
存在確認 Any some any?
最初の要素取得 First find first
最後の要素取得 Last findLast last
一意の要素取得 Distinct uniq uniq
和集合 Union union Set#union, Set#+
差集合 Except difference Set#difference, Set#-
積集合 Intersect intersection Set#intersection, Set#&
平坦化 SelectMany flatten flatten
スキップ Skip drop delete, delete_at
指定数要素取得 Take take take
合計計算 Sum sum inject(&:+), reduce(&:+), sum*1
最大値取得 Max max max
最小値取得 Min min min
集合関連のメソッドについて

集合に関してはArrayクラスではなくSetクラスのメソッドになります。
Setもうまく使うとかなり便利なので、この機に見直しておくのもおすすめです。
library set (Ruby 2.3.0)

合計計算について

合計計算のsumメソッドはRuby本体にはないのですが、RailsというかActiveSupportにはsumがあります。
Rails全部じゃなく、ActiveSupportだけ導入するのもアリだと思いますし、逆にRailsからRuby始めましたという人にとっては、当たり前にあるメソッドが実はActiveSupportのものだったという点を把握しておくと、後々役立つのではないかと思います。
ほかにも便利なメソッドが追加されるので、こちらも一度見ておくと色々捗りますよ。
Active Support コア拡張機能 | Rails ガイド

Symbol$to_procの話

で、「合計計算」がなんだかよくわからない感じになっているのですが、sumを使わない(使えない)場合はinject(もしくは別名のreduce)というたたみこみを行うメソッドを使います。
実はActiveSupportsumメソッドの実装もそうなってるのですけど。

injectメソッドを普通に書き下すと

[1, 2, 3, 4, 5].inject {|result, item| result += item }

みたいな感じになるのですが、ブロック内で単純なメソッドを適用するような場合に使われるinstance method Symbol#to_proc (Ruby 2.3.0)というものがあります。
それを使うと

[1, 2, 3, 4, 5].inject(&:+)

と、かなり短くかけちゃうので、結構使われてます。
injectに限らず、例えば微妙な例ですが、数値をすべて文字列にしたい場合も

[1, 2, 3, 4, 5].map { |item| item.to_s }

ではなく

[1, 2, 3, 4, 5].map(&:.to_s)

となります。
上のSymbol#to_procじゃない書き方すると、レビューで指摘されること間違いなしでしょう。

では。

*1:ActiveSupport使用時

LodashとLINQの対応表

こんにちは! 情報システム部の高野です。
前回、Lodashについて書きましたがその続きです。

Lodashを使ってるとLINQと同様の処理が名前違いで存在するので
サーバー処理とクライアント処理を行ったり来たり書いているとよく、あれ?
ってなることがあります。*1
(じゃ~linq.jsを使えばいいじゃないって意見はそっと棚の上に置いておくことにします)


ということで備忘録的に対応表を作っておこうじゃないかと。
(私がよく使う関数限定ですけど)

LINQ Lodash
射影 Select map
フィルター Where filter
昇順ソート OrderBy orderBy
降順ソート OrderByDescending orderBy
グループ化 GroupBy groupBy
存在確認 Any some
最初の要素取得 First find
最後の要素取得 Last findLast
一意の要素取得 Distinct uniq
和集合 Union union
差集合 Except difference
積集合 Intersect intersection
平坦化 SelectMany flatten
スキップ Skip drop
指定数要素取得 Take take
合計計算 Sum sum*2
最大値取得 Max max*3
最小値取得 Min min*4

名前だけ上げておくだけだとイマイチなので特徴的なものをいくつか掘り下げてみます。

使い方

まずは、この関数達をどのように使うのかって話なんですけど


LINQ

var result = src.Where(x => x.Name == "Hoge");


Lodash*5

var result = _(src).filter(x => x.name === "Hoge");

このように基本的にはLINQでもLodashでもほぼ同じような書き方になります。
一部、全然書き方が異なったり他の関数と混同しそうなものがあるのでご紹介します。

ソート

ソートは、LINQとLodashで大きく異なります。


LINQの場合は、昇順と降順で異なる関数を使うことになります。

var asc = src.OrderBy(x => x.Id);
var desc = src.OrderByDescending(x => x.Id);


そして結構はまるポイントなのが、複数キーによるソートです。

var result = src.OrderByDescending(x => x.Name).ThenBy(x => x.Id);

ThenByが正しいのですが、OrderByを重ねてもエラーにはならないので慣れてない人は間違うことが多いです。


一方、Lodash

var result = _(src).orderBy(["name", "id"], ["desc", "asc"]);

1つの関数で昇順・降順も書けますし複数ソートキーも1つの関数で書くことができます。
(文字列での指定なのがちょっと気になるところではありますが)

存在確認

コレクションの中身が存在するかどうかの確認を行うのが
LINQだとAny、Lodashだとsomeになります。
こちらは両方とも条件指定しなければコレクションが空かどうか判断できますし
条件を指定すればその条件に合致するものがあるかどうか判断できます。
Lodashには、これとは別にincludesという関数があります。


someの場合は、条件を式で渡すことができます。

var result = _(src).some(x => x.name === "Hoge"); // Hogeという名前があればtrue


includesの場合は、単純でその値があるかどうかだけを判定します。

var result = _([1,2,3]).includes(2); // この場合はtrue
最初の要素取得

最初の要素を取得するのは、LINQだとFirstかFirstOrDefaultです。
こちらは名前的には分かりやすいのですが
Lodashはfindになります。ちょっと分かりづらいですね。
これらは両方とも条件指定がなければコレクションの最初の要素を取得します。
条件指定すればその条件に合致する最初の要素を取得します。

その他にLodashには、headという関数があり
これがLINQのFirstと同じ動きをするように思えるのですが
こちらは、条件指定ができません。

var result = _([1,2,3]).head(); // この場合は1

紛らわしい……。

ページング

所謂、ページングを行う時に使う関数でLINQではSkipとTakeを使います。
Skipでシーケンスを読み飛ばしTakeで必要数だけ要素を取り出します。
これをLodashでやるには、dropとtakeを使うと同じように書けます。

_([0,1,2,3,4,5,6,7,8,9]).drop(2).take(5).forEach(x => console.log(x));
//  2 3 4 5 6

Skipとdropが同じとは思えなかったので探すのが大変でした。

まとめ

このような感じでLINQとLodashは、同じ名前の関数もあれば
全然違う名前で同じ処理をするものがあります。
また同じ処理を実行する関数でも指定方法が違ったりします。
LINQの関数名はSQLに影響されているものが多く
どちらかと言うとLINQの方が異端なのかもしれませんね。

*1:よく考えればJavaScript標準の集合関数も同じことが言えますね

*2:公式サイトのドキュメントだとMathというカテゴリになっているので注意

*3:sum同様

*4:sum同様

*5:ES5以前のJavaScriptだと書き方が異なります。ES2015かTypeScriptでお試しください。

アットホームオンラインシステム その2

情報システム部の豊田です。

前回、私が投稿させていただいた「アットホームオンラインシステム」の続きです。

  • 「アットホームオンラインシステム」とは、アットホームが加盟する不動産店に対して過去に提供していた、物件情報を業者間に公開するためのシステムです。

 「Windows95」は、パーソナルコンピューター業界にとても大きなインパクトを与えましたが、パソコンに興味のない、いわゆる「普通の人」にも大きな変化を起こしました。なにしろ発売日は解禁と同時に販売する為に深夜に店をオープンし、街はWindows95をいち早く手に入れたい人でごった返し、報道陣まで集まりニュースになりました。あまりの過熱ぶりに「Windows95」が何なのかをわからずにとりあえず買う人まで現れる始末。 これの影響により日本では一社独占といっても過言ではないパソコンの機種も、IBM PC/AT互換機に取って代わられて次の時代へ進みました。

 世間がこのような空気なので、当社でもWindows版のオンラインシステムの開発が急がれました。とりあえず、プロトタイプっぽいものを作ることになりましたが、当時は何の言語で作ればいいのかよくわからず、開発言語界隈も「オブジェクト指向」が注目を浴びていた頃で、「WindowsのアプリケーションはC++作る」「オブジェクト指向でないとWindowsのプログラムは作れない」といった事を聞きかじったので、C言語を多少つかえた私はそのまま軽い気持ちで会社に「Borland C++ and Application Framework 3.0」を買ってもらいました。自主的に家のパソコンにも同じコンパイラをインストール(当時98000円!!)して、書籍を買い込んでなんとかモノにしようとしたのですが、このハードルは乗り越えられませんでした。あの時は会社にも家族にも「ごめんなさい」でした・・・。

 そんな時、開発協力ベンダーさんから「Visual Basic 5.0」で作ることを提案されて話は一気に進みました。まだ「Windowsらしい、Windowsならでは」という理解があまり進んでなく、とりあえずWindowsで動作し、マウスで操作できるというシロモノでしたが、時代の流れに振り落されることなくオンラインシステムは生まれ変わりました。アットホームオンラインシステムとしては、これがバージョン5に当たります。ちなみに画面のプロトタイプは私が作りました。私たちもお客様もWindowsには比較的早く馴染んだこともあり、すぐにバージョン6にアップデートされ、Windowsらしいアプリケーションとなりました。

 このWindows版オンラインシステムはバージョン7まで行きましたが、マウスによる操作でキーボードから離れられたことと、ユーザインターフェースはお客様の声を積極的に取り込んでかなり作り込んだ為に、おそらくそれまでで一番使い勝手のよかった印象です。クライアントサイドはWindows版となりましたが、ホスト側は従来のシステムにもう一つレイヤーを被せて漢字コード変換などを行って、上り電文、下り電文なんて言葉が普通に飛び交ってました。この時にとても印象的だったのが、キーボードを使わずにマウスだけでほとんどの操作が可能だったのですが、従来の利用者からは「前のように番号で操作できるようにして欲しい」という「テンキーを使ったショートカット」の要望がとても多くあったことを思い出します。プロフェッショナルな方々が使うシステムなので、慣れてくると少しでも早い操作を望まれるということを知った時でもありました。

 合わせてWindows版オンラインシステムから派生した「アットホームNAVI」というタッチパネルで操作するキオスク端末も生まれました。従来のオンラインシステムは不動産業の方々にご利用いただく為のシステムでしたが、このアットホームNAVIは不動産屋さんの店頭に来たエンドユーザが不動産情報を検索し、気になる情報があれば接客カウンターにて相談するという流れを想定して作られたものでした。エンドユーザからすると、自分の希望条件がまだ曖昧な状態でも、このシステムを通して相場感や設備の違いを確認したり、住みたいエリアを見つける手助けとなり、不動産屋さんからすると、お客様に落ち着いてゆっくり探してもらえるということで、なかなか評判の良いシステムでした。

 このアットホームNAVIは、タッチパネルにタッチすると音がする愉快な仕様でしたが、社内での開発にてテストで触りまくっていたらフロア中のスタッフから「うるさい!」と言われたのはほのぼの(?)エピソードの一つです。この開発には深くは関わらなかったのですが、アットホームNAVIはWindowsで動作しており、不動産屋さんがお店を閉める時には、アプリケーションを終了してOSをシャットダウンしていただくというちょっと煩わしい作業が必要でした。これを一発でシャットダウンまで導く小さなプログラムを作成しました。プログラムそのものは小さなものでしたが、WindowsAPIのリファレンスマニュアルとにらめっこして作った最初のプログラムでもあったので、個人的に印象に残っているプログラムでした。そしてごくたまにシャットダウンしないことがあり、その課題も完璧にはクリアできなかったのも印象に残ってる理由かもしれません・・・。(その節は申し訳ありませんでした。)

 ここまでが各パーソナルコンピューターで動作するネイティブアプリケーションでサービスを提供していた時代です。

 私はバージョン6の開発が始まったくらいから、別の潮流に飲み込まれていました。そう、インターネットです。ウェブブラウザーです。ホームページです。当社でもコンシューマー向けのウェブサイトを立ち上げました。合わせて世の中のシステム開発領域ではダウンサイジングなんて言葉の波が押し寄せていましたね。そういったわけでしばらくはオンラインシステムから離れていて、コンシューマー向けサイトへ物件情報を掲載する為のスタティックページを生成するプログラムをVisulaBasicで書いていました。

 当社は不動産業を営む方々同士の不動産会社間情報流通がメインだったこともあり、当時は直接エンドユーザに不動産情報を届ける、消費者向け情報流通についてはまだ手探りの状態でした。一般的にも情報誌が主体で物件検索サイト自体が珍しかった頃です。こういった背景から「検索する」というほど物件数もありませんでしたので、ディレクトリツリー型のスタティックページで物件情報を公開していました。ちなみに私の作ったこのスタティックページ生成のプログラムが出来るまでは、担当部署の方はHTMLタグをテキストエディタで入力してページを作っていたという、今となっては驚きの業務でした。サービス提供したいという気持ちが先行して、環境や道具が整ってなくても力技で進んでいくスタイルは当社の社風なのかもしれません。

 そのプログラム自体はホントに簡単なプログラムだったのですが、ある程度自動化できたことによりかなり喜ばれました。それからずいぶんと月日が経ち、そのプログラムも使われなくなったはずなのに「あの時のあのプログラムは本当に助かりました」と事ある毎に言われて、「よっぽどHTMLのタグ打ちはツラかったんだな・・・」と思うと同時に「プログラムの中身が簡単であるとか難しいであるとかは関係ないよな」ということを知った時でもありました。

 最初にこの仕事を頼まれた時には「HTML」の意味は解らないし、「ネットスケープナビゲーター」が何を指しているのかもわかっていませんでしたが、この仕事によってHTMLへの理解が進み、ダイナミックにページ内容を書き換えるためにはどうしたらいいのかというのを試行錯誤していたことを思い出します。最初はWindows95に付属していた「Personal Web Server」と「Perl」を使って理解を深めていました。何にもわからない状態からだったので、概念そのものが腹落ちするまでとても苦労したことを思い出します。サンプルで引っ張ってきたperlスクリプトが動くまでに何度 "Internal Server Error"というメッセージを見たことか。今でも"cgi-bin"という文字列を見ると当時の多少ネガティブ寄りな気持ちを思い出します。幸いなことに所属部署内でも特殊な立ち位置だったので、この辺りの調査・研究・学習の時間はかなりあったのと、インターネットにはすでにこの手の話はたくさん公開されていたのに助けられました。こういった情報をシェアしていただいた先人の方々には感謝しかありません。

こうして、オンラインシステム畑から少し離れたところに置かれて、インターネットをほんの少しカジったところでポンと肩を叩かれるわけです。

「Web版のオンラインシステムって作れるか?」と。

Windows版オンラインをC++で作ろうとして挫折した苦い思い出がよみがえります。

しかし、その不安を超えるほどに私は当社のオンラインシステムが好きだったようです。

またしてもずいぶんと長くなりました。 ここから先のお話は、また次の機会に。

Lodashの2つの書き方について

情報システム部の高野です。
今日は、JavaScriptのライブラリ「Lodash」について書きます。

Lodashは、色々なことができるライブラリなのですが
私は、集合を扱う関数(C#でいうLINQ to Object)しかほぼ使っていません。
この集合を扱う関数で2種類の書き方があります。


※以降で扱うソースコードは、TypeScriptで記述します。


1つ目の書き方は、_メソッドの引数に配列を書く方法です。

_([1,2,3,4]).filter(x => x % 2 == 0).map(x => x * x).forEach(x => console.log(x));

// 4
// 16


2つ目の書き方は、filterなどのメソッドの引数に配列を書く方法です。

_.filter([1,2,3,4], x => x % 2 == 0).map(x => x * x).forEach(x => console.log(x));

// 4
// 16


この2つは、どちらも結果は同じになります。(違ったら困りますね)
では、なぜ2種類の書き方があるのかと言うと
1つ目は、遅延実行されるのです。

どういうことかを確認するため下記のように書き換えます。

_([1,2,3,4]).filter(x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).map(x => {
    console.log("map:" + x * x);
    return x;
});

これは、なにも出力されません。

_.filter([1,2,3,4], x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).map(x => {
    console.log("map:" + x * x);
    return x;
});

// filter:1
// filter:2
// filter:3
// filter:4
// map:2
// map:4

こちらは、普通に出力されます。
遅延実行は、必要な時がくるまで実行されないのです。

ではどうやって実行するかですが、下記のように書きます。

var a = _([1,2,3,4]).filter(x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).map(x => {
    console.log("map:" + x * x);
    return x;
});

a.value(); // LINQのToArray()と同じ

// filter:1
// filter:2
// filter:3
// filter:4
// map:2
// map:4

valueメソッドやforEachメソッド、sizeメソッドなどで処理が実行されます。
ちなみにmapメソッドの後にvalueメソッドを続けて書くことも、もちろんできます。

出力結果を見てC#を知っている方は違和感を持ったのではないでしょうか?
ここでC#で同じことをやってみます。

var sample = new []{1, 2, 3, 4};

sample.Where(x => { // filterは、linqではWhere
    WriteLine($"filter:{x}");
    return x % 2 == 0;
}).Select(x => {    // mapは、linqではSelect
    WriteLine($"map:{x * x}");
    return x;
}).ToArray();

// filter:1
// filter:2
// map:4
// filter:3
// filter:4
// map:16

mapが出てくるタイミングが違いますね。
C#LINQでは、ストリーム的に1つずつ次の処理に投げているのに対して
Lodashの場合は、1つのメソッドの処理を終えてから次のメソッドに投げています。

では、JavaScriptでは、ストリーム的に処理を流すことはできないのか?
linq.jsというライブラリで試してみましょう。

linq.from([1,2,3,4]).where(x => {
    console.log("filter:" + x);
    return x % 2 == 0;
}).select(x => {
    console.log("map:" + x * x);
    return x;
}).toArray();

// filter:1
// filter:2
// map:4
// filter:3
// filter:4
// map:16

C#とまったく同じになります。

それじゃlinq.jsの方が効率も良いし高速なのでは?
計測してみましょう。

このようなプログラムを作成し
Lodashの遅延実行・即時実行、linqjsで比較してみます。

let sample = linq.range(1, 100000).toArray();
for(let i = 0; i < 10; i++)
{
    console.time("timer");

    _(sample).filter(x => x % 2 == 0).map(x => x * x).value();

    console.timeEnd("timer");
}

10回の内の最大値と最小値を抜いた8回の平均です。

  • Lodash遅延実行・・・4.97ms
  • Lodash即時実行・・・26.6ms
  • linqjs・・・44.5ms

予想に反してLodashの遅延実行が一番速かったです。


ということでLodash使うなら基本的には遅延実行の方を使った方が良いでしょう。
でもRemoveメソッドとかは、即時実行の方が良いケースが多いと思うのでちゃんと使い分けるってことですかね。
※10万回ループしての差なので業務システムなどの場合は、どちらを選んでも大差無いと思われます。