読者です 読者をやめる 読者になる 読者になる

abcdefGets

ゲッツ!

Function.prototype.bind のパフォーマンスについて

ふとパフォーマンスが気になったので調査した。
記憶が正しければ、callよりも遅いはず。

というわけでレッツ検証

事前準備

package.json

{
  "name": "bench",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "author": "brn",
  "license": "MIT",
  "devDependencies": {
    "benchmark": "^2.1.3"
  }
}

bench.js

/**
 * @fileoverview
 * @author Taketoshi Aono
 */

const Benchmark = require('benchmark');
const suite = new Benchmark.Suite;

const bind = (() => {}).bind({});
const bindWithArgs = ((a, b, c) => {}).bind({}, 1, 2, 3);
const call = () => {};
const callargs = {};
const callcall = () => call.call(callargs)
// add tests
suite
  .add('bind', () => {
    bind();
  })
  .add('bind with args', () => {
    bindWithArgs();
  })
  .add('call', () => {
    callcall();
  })
  .on('cycle', function(event) {
    console.log(String(event.target));
  })
  .on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').map('name'));
  })
  .run({ 'async': true });

node bench.js

結果は V8限定ですが,

bind x 43,404,436 ops/sec ±2.07% (83 runs sampled)
bind with args x 35,140,882 ops/sec ±2.48% (84 runs sampled)
call x 59,048,983 ops/sec ±1.13% (85 runs sampled)
Fastest is call

となりました。 予想通り、callが最速。

ただ、気になるのはbindに引数を束縛した場合さらに遅くなる点。

気になるので調べました。

V8のコミットID
bdf32cf1bc96982ff5a22195d874617ffae03e79
時点のものです。

コード検証

色々さがして、とりあえずx87のbuiltin-x87.ccを見る。
x64でもいいけど、とりあえずx87を調べましょう。

でこれが、bindで生成した関数を呼び出すアセンブラコード生成関数。

void Builtins::Generate_CallBoundFunctionImpl(MacroAssembler* masm,
                                              TailCallMode tail_call_mode) {
  // ----------- S t a t e -------------
  //  -- eax : the number of arguments (not including the receiver)
  //  -- edi : the function to call (checked to be a JSBoundFunction)
  // -----------------------------------
  __ AssertBoundFunction(edi);

  if (tail_call_mode == TailCallMode::kAllow) {
    PrepareForTailCall(masm, eax, ebx, ecx, edx);
  }

  // Patch the receiver to [[BoundThis]].
  __ mov(ebx, FieldOperand(edi, JSBoundFunction::kBoundThisOffset));
  __ mov(Operand(esp, eax, times_pointer_size, kPointerSize), ebx);

  // Push the [[BoundArguments]] onto the stack.
  Generate_PushBoundArguments(masm);

  // Call the [[BoundTargetFunction]] via the Call builtin.
  __ mov(edi, FieldOperand(edi, JSBoundFunction::kBoundTargetFunctionOffset));
  __ mov(ecx, Operand::StaticVariable(ExternalReference(
                  Builtins::kCall_ReceiverIsAny, masm->isolate())));
  __ lea(ecx, FieldOperand(ecx, Code::kHeaderSize));
  __ jmp(ecx);
}

ここで注目したいのが、
Generate_PushBoundArguments(masm);
の部分
束縛した引数をPushするコードだと想像できる。

Generate_PushBoundArgumentsがこちら。

void Generate_PushBoundArguments(MacroAssembler* masm) {
  // ----------- S t a t e -------------
  //  -- eax : the number of arguments (not including the receiver)
  //  -- edx : new.target (only in case of [[Construct]])
  //  -- edi : target (checked to be a JSBoundFunction)
  // -----------------------------------

  // Load [[BoundArguments]] into ecx and length of that into ebx.
  Label no_bound_arguments;
  __ mov(ecx, FieldOperand(edi, JSBoundFunction::kBoundArgumentsOffset));
  __ mov(ebx, FieldOperand(ecx, FixedArray::kLengthOffset));
  __ SmiUntag(ebx);
  __ test(ebx, ebx);
  __ j(zero, &no_bound_arguments);
  {
    // ----------- S t a t e -------------
    //  -- eax : the number of arguments (not including the receiver)
    //  -- edx : new.target (only in case of [[Construct]])
    //  -- edi : target (checked to be a JSBoundFunction)
    //  -- ecx : the [[BoundArguments]] (implemented as FixedArray)
    //  -- ebx : the number of [[BoundArguments]]
    // -----------------------------------

    // Reserve stack space for the [[BoundArguments]].
    {
      Label done;
      __ lea(ecx, Operand(ebx, times_pointer_size, 0));
      __ sub(esp, ecx);
      // Check the stack for overflow. We are not trying to catch interruptions
      // (i.e. debug break and preemption) here, so check the "real stack
      // limit".
      __ CompareRoot(esp, ecx, Heap::kRealStackLimitRootIndex);
      __ j(greater, &done, Label::kNear);  // Signed comparison.
      // Restore the stack pointer.
      __ lea(esp, Operand(esp, ebx, times_pointer_size, 0));
      {
        FrameScope scope(masm, StackFrame::MANUAL);
        __ EnterFrame(StackFrame::INTERNAL);
        __ CallRuntime(Runtime::kThrowStackOverflow);
      }
      __ bind(&done);
    }

    // Adjust effective number of arguments to include return address.
    __ inc(eax);

    // Relocate arguments and return address down the stack.
    {
      Label loop;
      __ Set(ecx, 0);
      __ lea(ebx, Operand(esp, ebx, times_pointer_size, 0));
      __ bind(&loop);
      __ fld_s(Operand(ebx, ecx, times_pointer_size, 0));
      __ fstp_s(Operand(esp, ecx, times_pointer_size, 0));
      __ inc(ecx);
      __ cmp(ecx, eax);
      __ j(less, &loop);
    }

    // Copy [[BoundArguments]] to the stack (below the arguments).
    {
      Label loop;
      __ mov(ecx, FieldOperand(edi, JSBoundFunction::kBoundArgumentsOffset));
      __ mov(ebx, FieldOperand(ecx, FixedArray::kLengthOffset));
      __ SmiUntag(ebx);
      __ bind(&loop);
      __ dec(ebx);
      __ fld_s(
          FieldOperand(ecx, ebx, times_pointer_size, FixedArray::kHeaderSize));
      __ fstp_s(Operand(esp, eax, times_pointer_size, 0));
      __ lea(eax, Operand(eax, 1));
      __ j(greater, &loop);
    }

    // Adjust effective number of arguments (eax contains the number of
    // arguments from the call plus return address plus the number of
    // [[BoundArguments]]), so we need to subtract one for the return address.
    __ dec(eax);
  }
  __ bind(&no_bound_arguments);
}

想像よりなが~いーーー

__ j(zero, &no_bound_arguments);で引数束縛がなければjmpするコードを生成

その後は、BoundArgumentsを保存するstack領域を確保、リターンアドレスをstackに押し込んで、
BoundArgumentsをstackに押し込む。
これをループで行っているのでだいぶ遅そう。

Function.prototype.bindが引数束縛つきで遅くなるのはこれが理由っぽい。

まとめ

  • 最速は() => fn.call(this)形式
  • 次点でfn.bind(this)
  • 引数の束縛はかなり遅くなるので、() => fn.call(this, ...)のがおすすめ。

アロー関数のおかげでFunction.prototype.bind使い所がない。

React.js meetup × React Native meetup に参加した

IT javascript web プログラミング 勉強会

パネルディスカッション

参加者

@yosuke_furukawa

@koba04

@yositosi (Togetter CEO)

@janus_wel (CureApp CTO)

全体的にReactNativeはWebの技術をどう活かせるか、
ワンソース・マルチユースができるか等の話だった。
あとReactNativeの将来性とか 全体的にReactNativeはWebの技術をどう活かせるか、
ワンソース・マルチユースができるか等の話だった。
あとReactNativeの将来性とか

React Nativeの利点

アプリの開発に、使い慣れたReactの技術を活かせるのは大きいのかもしれない。
そのため、Webの開発者がアプリを開発するときには向いている。

苦労したこと

TogetterさんはReactNativeでアプリを実装した時に、広告の表示部分のブリッジが用意されていなかったので、
自分たちでブリッジを作った
でも、見よう見まねで作ったらすんなり動いた。

ソース共通化できるのか

画面・ページレベルのコンポーネントは分ける必要がある。
共通化するよりも、プラットフォーム毎の最適なデザインを目指すべき。
一つ一つの細かいコンポーネントは再利用できる。

ngCore

懐かしかったw
ReactNativeと似ている。

ngCoreが犯した間違え

  • クローズドソースだった
  • ホットコードプッシュの正当性

AppleからのBanは怖い

React Hot Loaderで開発を更に加速する

@endam

React Hot Loaderの使い方。
あんま使ったことなかったので、ちゃんと使わねばと思った。
jspmやめたら検討しよう。

Inside Bdash

@hokkacha

Bdashの作者による、Bdashの実装アーキテクチャ
Fluxで実装していて、ページ毎にActionCreatorを作り、コントローラにしていた。
SpringとかあのへんのMVCの残り香がした。

hyperapp

@jbucaran

qiita.com

hyperappの作者による、hyperappの紹介。
わずか1kbのRedux + VirtualDomのライブラリ。
たったこれだけのコード量で実装できているのに驚愕。
全コード合わせても300行ほどらしい。
普通に選択肢になるかもしれない。 

小回りの聞くWebViewの使い方

@niryuu

speakerdeck.com

WebViewでWebGLを使ったアプリの実装についての話

ReactNativeでFirebaseのネイティブSDKを操作する

t.co

FirebaseをReactNativeから使う方法についての発表
自分たちはNativeからWebViewのFirebaseを使っていたので、逆の話も聞けてよかった。

Androiderから見るReactNative

@operandOS

t.co

ガチのAndroid勢がReactNativeを使ってどうだったのかの発表。
やっぱ、Nativeの人はこれをメインで使う理由は無いよなぁと思った。
今からReactNative使う上での一番の障害かもしれない。
あと、ネイティブのコンポーネントとReactNativeのブリッジ書けばスター一杯のチャンスらしい。
書こうかな。

SideCI(スポンサーLT)

LintのCIツール。GitHubのPRから実行したり

CureApp(スポンサーLT)

CureAppの紹介。医師からjavascriptエンジニアになった異色のCEOの話。
面白そうなスタートアップだった。

まとめ

ReactNativeは非ゲームなら使っていってもいいと思う。
特にReduxで実装している場合は、Reducer等のドメインは使いまわせるはずなので、
インターフェースのディレクトリだけ分けて、後は使いまわすって言うのがかっこいいかも。
あとは、operandOSさんの発表にあったけども、
ガリガリにチューニングしたいところはネイティブで書いて、
それ以外の面倒なところはReactNativeで書くのがいいかもしれない。

とりあえず、ReactNativeは触っとかないとまずそう。

【RECRUIT Technologies NIGHT vol.5】リクルート流フロントエンド開発 に参加してきた

IT javascript web 勉強会 プログラミング

React / Redux を活用したリクルートテクノロジーズのフロントエンド開発

古川陽介 さん(@yosuke_furukawa)

speakerdeck.com

以下メモ

言いたいこと

フレームワークは作るものに合わせて作る

リクルートのWeb

トップページに検索があり、
検索するとリストビューがでるような一般的なwebサイト

典型的なWebサイト

最近はチャットできたり、
インタラクティブなやつができた。

要望

  • とにかくパフォーマンス
  • NatieアプリっぽいLook & Feel
  • Interactiveな動き(Chat や いいね通知)

これまでのWebフレームワークでは無理

React x Redux x Node でフレームワークを作っている

事例

フロントエンドを作る上でも気をつけていること

  • サーバでもクライアントでもレンダリングする
  • Historyを壊さない
    • 戻る・進むで状態を壊さない
    • 戻る・進む中にはレンダリングスキップする
  • Consumer Driven Contract
    • 従来バックエンドが決めていたAPIの仕様をフロントエンドが手動して要求を書く
    • フロントエンドが使いやすいAPIになる。

【翻訳】リッチなWebアプリケーションのための7つの原則 - from scratch

フロントエンドエンジニアが React Native を触ってみた話

90%ほどiOS/Androidで共有できる
Hot/Live Reloadingが早い

疑似要素が使いたい
疑似要素は無理

shorthandも使えない

cssがちょっと違和感があるので戸惑うかもしれない。

iOS/Androidの違い

プラットフォーム毎にUI/UXのベストプラクティスは違う

iOS/Android

  • Platform.OSで分ける
  • ファイル自体を分ける

何故ReactNativeなのか

  • ユーザー数の多いアプリで採用されている
  • Webとのコード共有ができる

ReactNativeアーキテクチャ

WebのReactをつかったこと Objc,Javaの知識は今の所必要なしに使える。

ReactNativeは想像以上に良さそうだった。
簡単なアプリならWebの延長で作れそうでよい!
是非取り入れて評価してみたい。

リクルートのフロントエンド改革に挑んだ話

太田さん

前提

外注がZip納品して、SvnでBackendが開発されている

フロントエンドがViewを見れないので、CSSの影響調査をすることができない

というまあひどい状況

改革

SIerの意識改革の話。
保守的なエンジニアのやり方を変えるにはどんなものでもいいから定量的な指標が必要で、
たとえ、LOC(Lines of codes 要はコード行数)であれ、前提あれば指標にするしかない。
でなんとかやり方の改革に成功した話。

エンジニアは保守的な人が多いのはまあ結構思う。
ただ、論理的に何%良くなったとか、定量的な指標をしっかり準備しておけば、説得できる人も多いのは確かだなと思ったし、
これはとても参考になる。

成長

バックエンド寄りのフロントエンドの人がCSSを学習するのは早いけど、
フロントエンド寄りの人がバックエンドを学ぶのはやはり時間がかかる。

きちんとした学習コンテンツを用意すべきだった。

ここはかなり同意。
ただ、学習コンテンツがどんなものか、どう動機づけするかがかなりの課題だなと思う。

まとめ

リクルートテクノロジーズさんは、というかリクルートさんは結構外注が多いイメージだったが、
内部でもかなり進んだことにチャレンジしていたっていう印象を受けた。
特にReactNativeのところは今の業務にも活かしていきたい(難しいけど)
全体的に実りのある勉強会だったなぁ。

V8 の For In の話

Ecmascript javascript プログラミング V8

V8 blogに話が出ていたが、V8のfor in構文が高速化したらしいので、
コードと共におっていこうと思う。

For In とは

for (const key in object) {

}

このような構文でObjectのキーをイテレーションする機能。
この構文を何故高速化したかというと、Facebookのコード実行の7% Wikipediaのコード実行の8%をForIn構文が占めていたらしく、
実行に負荷のかかる部分だったらしい。

一見この構文は単純に見えるが、処理系が行わなければならない仕事は以外に多く複雑だ。

まずはForIn構文の定義を見てみよう。上記のBlogにかかれているSpecを抜粋する。

EnumerateObjectProperties ( O ) 抽象命令であるEnumerateObjectPropertiesが引数Oを伴って呼び出される時、以下のステップをとる。
1. Type(O)がObjectか確認する。
2. Iteratorを返却する。IteratorのnextメソッドはOのenumerableな文字列のプロパティを全て列挙する。
IteratorオブジェクトはECMAScriptのコード中からアクセス不能である。プロパティの列挙順及びその方法は特に規定しないが、以下の規定に沿わなければならない。
A. Iteratorのthrowとreturnメソッドはnullであり、決して呼び出されない。
B. Iteratorのnextメソッドはオブジェクトのプロパティを処理して、次にiteratorの値として返すプロパティのキーを決める。
C. 戻り値のプロパティキーはSymbolを含まない
D. ターゲットのオブジェクトのプロパティは列挙中に削除されるかもしれない。
E. Iteratorのnextメソッドに処理される前に削除されたプロパティは無視される。もし、列挙中に新たなプロパティが追加された場合、新たに追加されたプロパティは現在の列挙中に処理される保証はない。
F. プロパティ名は列挙中に一度しかIteratorのnextメソッドによって返却されない。
G. 列挙するターゲットのオブジェクトのプロパティは、ターゲットのprototype、そのprototypeのprototypeも含むが、一度でも列挙されたプロパティのキーと同じキーを含んだprototypeのキーは列挙されない。(シャドウイングされたprototypeは列挙されない)
H. ptototypeのプロパティが既に処理されていた場合、[[Enumerable]]属性は考慮しない。
I. prototypeの列挙可能プロパティ名を取得する場合は、必ず、EnumerateObjectPropertiesをprototypeを引数に呼び出して取得しなければならない。
J. EnumerateObjectPropertiesはターゲットオブジェクトのプロパティを[[OwnPropertyKeys]]内部メソッドを呼び出して取得しなければならない。

かなり要件が複雑なのがわかる。

概要

Blogから抜粋

Map                               Descriptors              Enum Cache
--------------------------   ->   -------------------   -> -----------------  -> -------
|enumLength        |  3  |   |    | length    |   3 |   |  | Keys    | ptr | -|  | 'a' |
--------------------------   |    -------------------   |  -----------------     -------
|nofOwnDescriptors |  3  |   |    | EnumCache | ptr | --|  | indices | ptr |     | 'b' |
--------------------------   |    -------------------      -----------------     -------
|DescriptorArray   | ptr |---     | ...       | ... |                            | 'c' |
--------------------------        -------------------                            -------

MapのDescriptorが持っているEnumCacheからプロパティのキーリストを取得するようにしたので、高速になったらしい。

実装

さて実装はどうなっているか。

V8のForInはRuntime呼び出しで実装されている。
試しにd8で確認すると…

./d8 --print_code -e "for (const i in {__proto__: {a:1}}){}"

この行でForInEnumerateをRuntimeから呼び出しているのが確認できる。

0x1cb5dca0476e   398  b801000000     movl rax,0x1
0x1cb5dca04773   403  48bbc0ddc70001000000 REX.W movq rbx,0x100c7ddc0    ;; external reference (Runtime::ForInEnumerate)
0x1cb5dca0477d   413  e81efadfff     call 0x1cb5dc8041a0     ;; code: STUB, CEntryStub, minor: 8

ではRuntime::ForInEnumerateを確認しよう。

ちなみにMapとかV8の基礎情報は V8祭り を参照してほしい。

では確認ー

ForInEnumerateはsrc/runtime/runtime-forin.ccにある。

Runtime::ForInEnumerateを確認する前にこのファイルにある他のRuntimeも確認しておく。

このRuntimeに用意されているのは、

  • Runtime_ForInEnumerate
  • Runtime_ForInPrepare
  • Runtime_ForInHasProperty
  • Runtime_ForInFilter

の4つ。
ForInEnumerate以外はIgnitionインタープリタから呼び出されているようだが、今回はFullCodegenから呼び出されるコードをメインに見ていく。

RUNTIME_FUNCTION(Runtime_ForInEnumerate) {
  HandleScope scope(isolate);
  DCHECK_EQ(1, args.length());
  CONVERT_ARG_HANDLE_CHECKED(JSReceiver, receiver, 0);
  RETURN_RESULT_OR_FAILURE(isolate, Enumerate(receiver));
}

これがForInEnumerateの本体だ。
中ではさらにEnumerateをreceiverを引数に呼び出している。

次はEnumerate

// Returns either a FixedArray or, if the given {receiver} has an enum cache
// that contains all enumerable properties of the {receiver} and its prototypes
// have none, the map of the {receiver}. This is used to speed up the check for
// deletions during a for-in.
MaybeHandle<HeapObject> Enumerate(Handle<JSReceiver> receiver) {
  Isolate* const isolate = receiver->GetIsolate();
  JSObject::MakePrototypesFast(receiver, kStartAtReceiver, isolate);
  FastKeyAccumulator accumulator(isolate, receiver,
                                 KeyCollectionMode::kIncludePrototypes,
                                 ENUMERABLE_STRINGS);
  accumulator.set_is_for_in(true);
  // Test if we have an enum cache for {receiver}.
  if (!accumulator.is_receiver_simple_enum()) {
    Handle<FixedArray> keys;
    ASSIGN_RETURN_ON_EXCEPTION(
        isolate, keys, accumulator.GetKeys(GetKeysConversion::kKeepNumbers),
        HeapObject);
    // Test again, since cache may have been built by GetKeys() calls above.
    if (!accumulator.is_receiver_simple_enum()) return keys;
  }
  return handle(receiver->map(), isolate);
}

さて、このEnumerateで注目したいのは、FastKeyAccumulatorである。このFastKeyAccumulatorがオブジェクトのprototypeの情報、プロパティの情報を取得し、
最適な処理に振り分けている。

void FastKeyAccumulator::Prepare() {
  DisallowHeapAllocation no_gc;
  // Directly go for the fast path for OWN_ONLY keys.
  if (mode_ == KeyCollectionMode::kOwnOnly) return;
  // Fully walk the prototype chain and find the last prototype with keys.
  is_receiver_simple_enum_ = false;
  has_empty_prototype_ = true;
  JSReceiver* last_prototype = nullptr;
  for (PrototypeIterator iter(isolate_, *receiver_); !iter.IsAtEnd();
       iter.Advance()) {
    JSReceiver* current = iter.GetCurrent<JSReceiver>();
    bool has_no_properties = CheckAndInitalizeEmptyEnumCache(current);
    if (has_no_properties) continue;
    last_prototype = current;
    has_empty_prototype_ = false;
  }
  if (has_empty_prototype_) {
    is_receiver_simple_enum_ =
        receiver_->map()->EnumLength() != kInvalidEnumCacheSentinel &&
        !JSObject::cast(*receiver_)->HasEnumerableElements();
  } else if (last_prototype != nullptr) {
    last_non_empty_prototype_ = handle(last_prototype, isolate_);
  }
}

これがFastKeyAccumulatorの初期化処理。やっていることは

  • Receiverオブジェクトのprototypeを辿る
  • prototypeにプロパティがあれば、has_empty_prototype_falseにする。
  • そして、全てのprototypeを辿り終わったら、has_empty_prototype_ == trueの場合は、receiverのMapオブジェクトがEnumを持っていて、Enumerableなプロパティを持っていないければ、is_receiver_simple_enum_ = trueになる。
  • それ以外の場合はlast_non_empty_prototype_に最後のprototypeを渡す。

さて、runtime-forinのEnumerate関数に戻ると、

if (!accumulator.is_receiver_simple_enum()) {

FastKeyAccumulator::is_receiver_simple_enum()で処理を分けている。
つまり、自身にEnumerableなプロパティを持たず、prototypeにも持たず、MapにEnumを持っているオブジェクトはこのファストパスに入る。
このパスではFastKeyAccumulator::GetKeys()でObjectのプロパティキーを取得する。

MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
    GetKeysConversion keys_conversion) {
  if (filter_ == ENUMERABLE_STRINGS) {
    Handle<FixedArray> keys;
    if (GetKeysFast(keys_conversion).ToHandle(&keys)) {
      return keys;
    }
    if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
  }

  return GetKeysSlow(keys_conversion);
}

GetKeysの定義は結構簡単で、filter_プロパティは今回はENUMERABLE_STRINGSなのでifに入る。
そしたらFastKeyAccumulator::GetKeysFastを呼び出してHandle<FixedArray>に変換する。

MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysFast(
    GetKeysConversion keys_conversion) {
  bool own_only = has_empty_prototype_ || mode_ == KeyCollectionMode::kOwnOnly;
  Map* map = receiver_->map();
  if (!own_only || !OnlyHasSimpleProperties(map)) {
    return MaybeHandle<FixedArray>();
  }

  // From this point on we are certiain to only collect own keys.
  DCHECK(receiver_->IsJSObject());
  Handle<JSObject> object = Handle<JSObject>::cast(receiver_);

  // Do not try to use the enum-cache for dict-mode objects.
  if (map->is_dictionary_map()) {
    return GetOwnKeysWithElements<false>(isolate_, object, keys_conversion);
  }
  int enum_length = receiver_->map()->EnumLength();
  if (enum_length == kInvalidEnumCacheSentinel) {
    Handle<FixedArray> keys;
    // Try initializing the enum cache and return own properties.
    if (GetOwnKeysWithUninitializedEnumCache().ToHandle(&keys)) {
      if (FLAG_trace_for_in_enumerate) {
        PrintF("| strings=%d symbols=0 elements=0 || prototypes>=1 ||\n",
               keys->length());
      }
      is_receiver_simple_enum_ =
          object->map()->EnumLength() != kInvalidEnumCacheSentinel;
      return keys;
    }
  }
  // The properties-only case failed because there were probably elements on the
  // receiver.
  return GetOwnKeysWithElements<true>(isolate_, object, keys_conversion);
}

FastKeyAccumulator::GetKeysFastの定義がこちら。
いろいろコードがあるのだが、重要なのは、is_dictionary_mapのチェックである。
is_dictionary_mapは拡張されるObjectの場合trueになっている。

現在の所、

  • グローバルオブジェクトのMap、
  • Object.create(null)の戻り値のMap

dictionary_mapという扱いになっている。

このオブジェクトの場合はそもそもTransitionするMapを持っていない、EnumCacheも持っていないので、
GetOwnKeysWithElementsのtemplate引数をfalseで呼び出す。
それ以外のオブジェクトの場合は、enum_cacheの有無をチェックし、
存在しなければGetOwnKeysWithUninitializedEnumCacheを呼び出し、その場で作成する。 GetOwnKeysWithUninitializedEnumCacheは省略するが、enum_cacheをdescriptorから作成し、プロパティ一覧を返す。 enum_cacheを既に持っているオブジェクトの場合、GetOwnKeysWithElementsがtemplate引数にtrueを渡して呼び出される。

template <bool fast_properties>
MaybeHandle<FixedArray> GetOwnKeysWithElements(Isolate* isolate,
                                               Handle<JSObject> object,
                                               GetKeysConversion convert) {
  Handle<FixedArray> keys;
  ElementsAccessor* accessor = object->GetElementsAccessor();
  if (fast_properties) {
    keys = GetFastEnumPropertyKeys(isolate, object);
  } else {
    // TODO(cbruni): preallocate big enough array to also hold elements.
    keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);
  }
  MaybeHandle<FixedArray> result =
      accessor->PrependElementIndices(object, keys, convert, ONLY_ENUMERABLE);

  if (FLAG_trace_for_in_enumerate) {
    PrintF("| strings=%d symbols=0 elements=%u || prototypes>=1 ||\n",
           keys->length(), result.ToHandleChecked()->length() - keys->length());
  }
  return result;
}

これがGetOwnKeysWithElementsの実装。
template引数がtrueの場合はGetFastEnumPropertyKeysを呼び出す。

// Initializes and directly returns the enume cache. Users of this function
// have to make sure to never directly leak the enum cache.
Handle<FixedArray> GetFastEnumPropertyKeys(Isolate* isolate,
                                           Handle<JSObject> object) {
  Handle<Map> map(object->map());
  bool cache_enum_length = map->OnlyHasSimpleProperties();

  Handle<DescriptorArray> descs =
      Handle<DescriptorArray>(map->instance_descriptors(), isolate);
  int own_property_count = map->EnumLength();
  // If the enum length of the given map is set to kInvalidEnumCache, this
  // means that the map itself has never used the present enum cache. The
  // first step to using the cache is to set the enum length of the map by
  // counting the number of own descriptors that are ENUMERABLE_STRINGS.
  if (own_property_count == kInvalidEnumCacheSentinel) {
    own_property_count =
        map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS);
  } else {
    DCHECK(
        own_property_count ==
        map->NumberOfDescribedProperties(OWN_DESCRIPTORS, ENUMERABLE_STRINGS));
  }

  if (descs->HasEnumCache()) {
    Handle<FixedArray> keys(descs->GetEnumCache(), isolate);
    // In case the number of properties required in the enum are actually
    // present, we can reuse the enum cache. Otherwise, this means that the
    // enum cache was generated for a previous (smaller) version of the
    // Descriptor Array. In that case we regenerate the enum cache.
    if (own_property_count <= keys->length()) {
      isolate->counters()->enum_cache_hits()->Increment();
      if (cache_enum_length) map->SetEnumLength(own_property_count);
      return ReduceFixedArrayTo(isolate, keys, own_property_count);
    }
  }

  if (descs->IsEmpty()) {
    isolate->counters()->enum_cache_hits()->Increment();
    if (cache_enum_length) map->SetEnumLength(0);
    return isolate->factory()->empty_fixed_array();
  }

  isolate->counters()->enum_cache_misses()->Increment();

  Handle<FixedArray> storage =
      isolate->factory()->NewFixedArray(own_property_count);
  Handle<FixedArray> indices =
      isolate->factory()->NewFixedArray(own_property_count);

  int size = map->NumberOfOwnDescriptors();
  int index = 0;

  for (int i = 0; i < size; i++) {
    PropertyDetails details = descs->GetDetails(i);
    if (details.IsDontEnum()) continue;
    Object* key = descs->GetKey(i);
    if (key->IsSymbol()) continue;
    storage->set(index, key);
    if (!indices.is_null()) {
      if (details.location() == kField) {
        DCHECK_EQ(kData, details.kind());
        FieldIndex field_index = FieldIndex::ForDescriptor(*map, i);
        int load_by_field_index = field_index.GetLoadByFieldIndex();
        indices->set(index, Smi::FromInt(load_by_field_index));
      } else {
        indices = Handle<FixedArray>();
      }
    }
    index++;
  }
  DCHECK(index == storage->length());

  DescriptorArray::SetEnumCache(descs, isolate, storage, indices);
  if (cache_enum_length) {
    map->SetEnumLength(own_property_count);
  }
  return storage;
}

GetFastEnumPropertyKeysがこれ。
実はGetFastEnumPropertyKeysは先程省略した、GetOwnKeysWithUninitializedEnumCacheからも呼び出される。
とても長いコードだが、やっていることはdescriptorがenum_cacheを持っていれば、それを返すし、なければ作成して返す。
これだけ。

さて、これがForInでキーを列挙する最速のパスなのだが、遅いパターンはどうだろうか。 GetOwnKeysWithElementsのに戻るが、

keys = KeyAccumulator::GetOwnEnumPropertyKeys(isolate, object);

と、KeyAccumulator::GetOwnEnumPropertyKeysを呼び出すらしい。

Handle<FixedArray> KeyAccumulator::GetOwnEnumPropertyKeys(
    Isolate* isolate, Handle<JSObject> object) {
  if (object->HasFastProperties()) {
    return GetFastEnumPropertyKeys(isolate, object);
  } else if (object->IsJSGlobalObject()) {
    return GetOwnEnumPropertyDictionaryKeys(
        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
        object->global_dictionary());
  } else {
    return GetOwnEnumPropertyDictionaryKeys(
        isolate, KeyCollectionMode::kOwnOnly, nullptr, object,
        object->property_dictionary());
  }
}

これが実装
object->HasFastPropertiesであれば、GetFastEnumPropertyKeysに戻れるらしい。
HasFastPropertiesである条件は、

  • mapがHashTableでなく、StringTableでも無いこと。

その条件の場合はやはり、enum_cacheから取得される。
それ以外のグローバルオブジェクトの場合、GetOwnEnumPropertyDictionaryKeysのパスに入る。
GetOwnEnumPropertyDictionaryKeysでは
各dictionaryのCopyEnumKeysToが呼び出されプロパティのコピーが行われる。

template <typename Derived, typename Shape, typename Key>
void Dictionary<Derived, Shape, Key>::CopyEnumKeysTo(
    Handle<Dictionary<Derived, Shape, Key>> dictionary,
    Handle<FixedArray> storage, KeyCollectionMode mode,
    KeyAccumulator* accumulator) {
  DCHECK_IMPLIES(mode != KeyCollectionMode::kOwnOnly, accumulator != nullptr);
  Isolate* isolate = dictionary->GetIsolate();
  int length = storage->length();
  int capacity = dictionary->Capacity();
  int properties = 0;
  for (int i = 0; i < capacity; i++) {
    Object* key = dictionary->KeyAt(i);
    bool is_shadowing_key = false;
    if (!dictionary->IsKey(isolate, key)) continue;
    if (key->IsSymbol()) continue;
    PropertyDetails details = dictionary->DetailsAt(i);
    if (details.IsDontEnum()) {
      if (mode == KeyCollectionMode::kIncludePrototypes) {
        is_shadowing_key = true;
      } else {
        continue;
      }
    }
    if (dictionary->IsDeleted(i)) continue;
    if (is_shadowing_key) {
      accumulator->AddShadowingKey(key);
      continue;
    } else {
      storage->set(properties, Smi::FromInt(i));
    }
    properties++;
    if (mode == KeyCollectionMode::kOwnOnly && properties == length) break;
  }

  CHECK_EQ(length, properties);
  DisallowHeapAllocation no_gc;
  Dictionary<Derived, Shape, Key>* raw_dictionary = *dictionary;
  FixedArray* raw_storage = *storage;
  EnumIndexComparator<Derived> cmp(static_cast<Derived*>(*dictionary));
  Smi** start = reinterpret_cast<Smi**>(storage->GetFirstElementAddress());
  std::sort(start, start + length, cmp);
  for (int i = 0; i < length; i++) {
    int index = Smi::cast(raw_storage->get(i))->value();
    raw_storage->set(i, raw_dictionary->KeyAt(index));
  }
}

でこれが、CopyEnumKeysToの実装。
forでループを回してプロパティをコピーしている。
まあこれが早いわけないよねということで、ForInの高速化の実装を確かめた。

まとめ

ForInのRuntime呼び出しでは、レシーバオブジェクトがFastPropertiesさえ持っていれば、
EnumCacheから値を取得するので高速。 IgnitionがForInを処理するパスについてはまたそのうち。

「Angular 4 の最新動向と、2017年再注目のDart、そしてAngular Dart」に行ってきた

勉強会 web javascript IT

AngularとDartの勉強会でした。

以下メモ

Angular4がやってくる!?新機能ダイジェスト

Asai Masahiko

Angular 4がやってくる!? 新機能ダイジェスト.pdf - Google ドライブ

  • Semantic Versioningの導入
  • 非推奨ポリシーの導入(2つのメジャーリリースを挟む)

変更点ダイジェスト

  • ViewEngineの改善
  • templateタグが非推奨に
  • @angular/animationsの独立
  • typescript2.1へのアップデート
  • metaタグの追加・更新・削除が可能に
  • Email Validator・EqualTo Validatorの追加

Angular2で作った社内向けツールを4に移行してみた

ハーモニーお菓子管理 お菓子の在庫が見れる

14 Components 4 Service 5 Route

およそ半日くらいで移行完了

まとめ

SemanticVersioningが導入後発のMajor Release 思っていたより小さなアップデート ViewEngineの改善はでかい

15分で分かった気になるDart

docs.google.com

小林達(さとし)

Dartを取り巻く状況

当初はjsを置き換える予定だった
AdSense/AdWordsがangular-dartにリプレイス
The new Google AdSense user interface: built with AngularDart
The new AdWords UI uses Dart — we asked why

Dart言語

発表から6年だが、まだ活発
DartPadで試せる
(DartPad)

すべてオブジェクト
intなどは初期化しないとnull
boolはtrue以外はすべてfalse
dart:collectionパッケージには(java)のような感じでコレクションがある。

  • Map
  • Iterable(List,Set)
  • 高階関数もつかえる
  • 型推論はもうちょい
  • Future・async・await
  • ドット2つでビルダー化する(thisを返す様になる)
  • 充実した標準ライブラリ
  • 公式ドキュメントが充実

Sound Dart

Stroing Mode

AngularDartで快適SPA

docs.google.com

AngularDartの歴史

TypeScript版と分離した

ビジネスに使える

AngularDartをさっと見る

Streamが言語自体に組み込まれている

サーバサイドDartを試してみる

docs.google.com

All-in-oneならAqueduct

シンプルなら純正のShelfがいいかも

初心者にはとっつきづらい

中間なのはRedStone
Shelfを利用したWrapper

まとめ

とりあえず、Dartの開発がまだ続いているのに驚いた(結構活発に)
けど、ES2015 + Typescriptの躍進があったので、他のオプショナル静的型の言語がフロントエンドで使われるには、
なかなか厳しい環境だろうなと思う。
ただ、Dartはランタイムがかなり充実している印象なので、それはうらやましい。
AngularDartがAngularと全く別のラインで開発されてるのも知らんかった。
が、まあ使わないだろう。

Typescript 2.2.0 の Mixin を使ってDIしてみる

typescript IT web プログラミング

表題の通り。
今まではMixinがなかったので、泥臭く型チェックができない方法(文字列とか)でDIしていたが、
typescript 2.2.0からMixinに対応したので、DIを考察する。

Dependency Injectionとは

Dependency Injectionとはその名の通り依存性を外から注入する仕組み。
なにが嬉しいかというと、クラス内部で依存しているはずのクラスを外部からコントロールできるようにすることで、
クラスの内部実装と外部への依存を疎結合にし、テストし易いクラスを作ることができる。

猿でも分かる! Dependency Injection: 依存性の注入 - Qiita
Inversion of Control コンテナと Dependency Injection パターン

DI for Typescript

さて、Typescriptで上述のDependency Injectionを実現するにはどういうパターンがあるか、検証してみたい。

public Dependency パターン

名前は勝手につけました。
その名の通りpublicプロパティに依存性を設定する。

実装

// deps1.ts

export interface Deps1 {
  hello(): string;
}

export class Deps1Impl implements Deps1 {
  public hello() {return 'hello'}
}


// deps2.ts

export interface Deps2 {
  world(): string;
}

export class Deps2Impl implements Deps2 {
  public world() {return 'world'}
}


// deps1+deps2-module.ts

import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';

export interface Deps1Deps2Module {
  deps1: Deps1;
  deps2: Deps2;
}


// deps1+deps2-module-impl.ts

import {
  Deps1Deps2Module
} from './deps1+deps2-module';
import {
  Deps1Impl
} from './deps1';
import {
  Deps2Impl
} from './deps2';

type Constructor<T> = new(...args: any[]) => T;

export function Deps1Deps2ModuleImpl<T extends Constructor<Deps1Deps2Module>>(Base: T) {
  return class extends Base {
    constructor(...a) {
      super(...a);
      this.deps1 = new Deps1Impl();
      this.deps2 = new Deps2Impl();
    }
  };
}


// inject-target.ts
import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';
import {
  Deps1Deps2Module
} from './deps1+deps2-module';


export class InjectTarget implements Deps1Deps2Module {
  public deps1: Deps1;
  public deps2: Deps2;

  public greet() {return `${this.deps1.hello()} ${this.deps2.world()}`}
}


// injected.ts

import {
  InjectTarget
} from './inject-target'
import {
  Deps1Deps2ModuleImpl
} from './deps1+deps2-module-impl';


export class Injected extends Deps1Deps2ModuleImpl(InjectTarget) {
}


// main.ts

import {
  Injected
} from './injected';


const injected = new Injected();
console.log(injected.greet());

deps1.ts deps2.ts

これらは単純な依存クラス

deps1+deps2-module.ts

こちらはdeps1とdeps2を注入される側の規約を定義したinterfaceクラス
依存性を注入される側はこのインターフェースを実装する。

deps1+deps2-module-impl.ts

依存性注入を実行するMixin関数。
ここで実際に依存性の注入を行う。

inject-target.ts

依存性を注入されるクラス。
interface Deps1Deps2Moduleをimplementsしている以外は、通常のクラスと変わりない。

injected.ts

ここでDeps1Deps2ModuleImpl関数にInjectTargetクラスを渡したクラスを継承することで、
依存性が注入されるクラスを生成する。

main.ts

エントリーポイント

問題点

publicプロパティに実装するしか無いので、カプセル化に問題がある。
防御的なクラスが作りづらい。

method injection パターン

こちらは上記の問題を踏まえ、publicメソッドを使ってinjectionの実現をする。

実装

// deps1+deps2-module.ts

import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';

export interface Dependencies {
  deps1: Deps1;
  deps2: Deps2;
}

export interface Deps1Deps2Module {
  __setDependencies(deps: Dependencies): void;
}


// deps1+deps2-module-impl.ts

import {
  Deps1Deps2Module
} from './deps1+deps2-module';
import {
  Deps1Impl
} from './deps1';
import {
  Deps2Impl
} from './deps2';

type Constructor<T> = new(...args: any[]) => T;

export function Deps1Deps2ModuleImpl<T extends Constructor<Deps1Deps2Module>>(Base: T) {
  return class extends Base {
    constructor(...a) {
      super(...a);
      this.__setDependencies({deps1: new Deps1Impl(), deps2: new Deps2Impl()});
    }
  };
}


// inject-target.ts

import {
  Deps1
} from './deps1';
import {
  Deps2
} from './deps2';
import {
  Deps1Deps2Module
} from './deps1+deps2-module';


export class InjectTarget implements Deps1Deps2Module {
  private deps1: Deps1;
  private deps2: Deps2;

  public greet() {return `${this.deps1.hello()} ${this.deps2.world()}`}

  public __setDependencies({deps1, deps2}) {
    this.deps1 = deps1;
    this.deps2 = deps2;
  }
}

変更点

deps1+deps2-module.ts

__setDependenciesというpublicなセッタメソッドを用意して、
外部から直接interfaceを触れられないようにした。

deps1+deps2-module-impl.ts

__setDependeciesに依存関係を渡すように修正

inject-target.ts

__setDependenciesを実装

問題点

__setDependenciesがpublicメソッドなのは変わらず。

考察

ほんとはconstructor injectionが型チェック付きでできれば良いのだが…
今回のMixinでは不可能なので、method injectionでやるのが一番良いのかなと思う。

元々はScalaのCakeパターンとかあのへんの感じでやりたかった(願望)
ただ、typescriptの貧弱な言語機能でもここまで頑張れるのがわかったので良かった。

とりあえず、文字列ベースのDIコンテナを使うよりはいいかもしれないので、一旦プロダクトで使ってみようと思う。
可能性を伸ばしていきたい。
伸びしろですねぇ!

今回の実装はこちらにありまぁーす

GitHub - brn/typescript-mixin-di-sample: Sample Implementation for typescript dependency injection by class mixin pattern.

jspmでtypescriptの開発をする

IT typescript web javascript プログラミング

jspmからtypescriptの開発をした時の備忘録を書く

typescriptコンパイラ + jspm

まずはjspmでtypescriptをランタイムコンパイルできるようにするため、
plugin-typescriptをインストールする。

cli

jspm install ts

jspm.config.js

SystemJS.config({
  ...
  packages: {
    "/src": {
      "defaultExtension": "ts",
      "meta": {
        "*.ts": {
          "loader": "ts"
        }
      }
    }
  }
});

これだけでtypescriptをランタイムで使う準備ができた。

typescriptのバージョンを変える

typescriptのバージョンを開発環境に合わせて変える。

まず、好みのバージョンのtypescriptをインストールする。

cli

jspm install typescript@2.2.1

インストールしたら、plugin-typescriptのtypescriptを更新する。

jspm.config.js

SystemJS.config({
  ...
  map: {
    "github:frankwallis/plugin-typescript@5.3.1": {
      "map": {
        "typescript": "npm:typescript@<バージョン>"
      }
    },
  }
}

<バージョン>のところに先程インストールしたtypescriptのバージョンを入れる。

typescriptのオプション

これもjspm.config.jsで設定する。

SystemJS.config({
  typescriptOptions: {
    "tsconfig": true, // tsconfigを使う場合はtrueにする
    "typeCheck": false // 型チェックするかどうか
  },
});

tsconfigプロパティがtrueの場合は、ページのルートからtsconfig.jsonを探すので、
ファイルの置き場所に注意。
typeCheckはtrue・false以外に'strict'も指定できる。

tsconfigプロパティがfalseの場合はここに全てのオプションを設定する必要がある。

typescriptの設定

tsconfig.json

module: system

を設定しておけば、import構文が全てSystemJS化するので、 jspm経由でロードできる。

以上