LoginSignup
11
7

More than 1 year has passed since last update.

ES2022のArray.prototype.atは今の所遅い (2021/10/16時点)

Last updated at Posted at 2021-10-16

Array.prototype.atは何ができるの

Pythonのように, 負数を指定すると配列の後ろから取得できます. 特に最後尾の数個や, 文字列での末尾からの指定など, 待ち望まれていた機能です. (String.prototype.atもあります)

const arr = [1, 2, 3]
console.log(arr.at(-1)); // 3

速度

普通の添字で指定と速度を比べてみます.

バージョンは

> node -v
v16.9.0

ソース

const arr = [...Array(1000*1000)].map((_, i) => i);
// for内の余分な計算は取っ払いたいので先に
const len = arr.length;
const len2 = len - 1;
const nlen = -len;

// [] 昇順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = 0; i < len; ++i) {
            arr[i];
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`インデックス昇順: ${endTime - startTime}ms`);
}

// [] 降順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = len2; i >= 0; --i) {
            arr[i];
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`インデックス降順 ${endTime - startTime}ms`);
}

// at 昇順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = 0; i < len; ++i) {
            arr.at(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`at昇順 ${endTime - startTime}ms`);
}

// at 降順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = -1; i >= nlen; --i) {
            arr.at(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`at降順 ${endTime - startTime}ms`);
}

結果

インデックス昇順: 47ms
インデックス降順 82ms
at昇順 4189ms
at降順 4462ms

びっくりするほど遅いです.

Chromeでも大体同じ傾向だったため, Nodejsの実装が特別悪いというわけでもなさそうです.

polyfillのほうが大幅に速い?

からコードを持ってきて, 簡易的に試してみます.

Array.prototype.myat = function (n) {
    // ToInteger() abstract op
    n = Math.trunc(n) || 0;
    // Allow negative indexing from the end
    if (n < 0) n += this.length;
    // OOB access is guaranteed to return undefined
    if (n < 0 || n >= this.length) return undefined;
    // Otherwise, this is just normal property access
    return this[n];
}

// myat 昇順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = 0; i < len; ++i) {
            arr.myat(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`myat昇順 ${endTime - startTime}ms`);
}

// myat 降順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = -1; i >= mlen; --i) {
            arr.myat(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`myat降順 ${endTime - startTime}ms`);
}
myat昇順 139ms
myat降順 101ms

速いです. 一瞬バグではないかと思いましたが, 添字にNaNや文字列を入れたりしても挙動は合っていたので間違いではないようです. ループ数も100で合っています.

最初は遅い理由を関数のオーバーヘッドガーや単純に処理が多いからーなどと考えていましたが, そう簡単ではないようです. この先は実装を解明してもらえる方に… チラッ

現状組み込みのArray.prototype.atがびっくりするくらい遅いだけで, 本来は添字に近い性能が出るのではないかと思います. Webアプリ開発における普段使いでは大きな影響はないと思いますが, 将来的によく使う機能になると思うので今後の性能改善に期待です.

全体ソース
const arr = [...Array(1000*1000)].map((_, i) => i);
const len = arr.length;
const len2 = len - 1;
const nlen = -len;

Array.prototype.myat = function (n) {
    // ToInteger() abstract op
    n = Math.trunc(n) || 0;
    // Allow negative indexing from the end
    if (n < 0) n += this.length;
    // OOB access is guaranteed to return undefined
    if (n < 0 || n >= this.length) return undefined;
    // Otherwise, this is just normal property access
    return this[n];
}

// [] 昇順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = 0; i < len; ++i) {
            arr[i];
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`インデックス昇順: ${endTime - startTime}ms`);
}

// [] 降順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = len2; i > 0; --i) {
            arr[i];
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`インデックス降順 ${endTime - startTime}ms`);
}

// at 昇順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = 0; i < len; ++i) {
            arr.at(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`at昇順 ${endTime - startTime}ms`);
}

// at 降順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = -1; i >= nlen; --i) {
            arr.at(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`at降順 ${endTime - startTime}ms`);
}

// myat 昇順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = 0; i < len; ++i) {
            arr.myat(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`myat昇順 ${endTime - startTime}ms`);
}

// myat 降順
{
    const startTime = (new Date()).getTime();
    for (let n = 0; n < 100; ++n) {
        for (let i = -1; i >= nlen; --i) {
            arr.myat(i);
        }
    }
    const endTime = (new Date()).getTime();
    console.log(`myat降順 ${endTime - startTime}ms`);
}

11
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7