42

allocまたはautorelease初期化子を使用する方が良い(より高速で効率的)ですか?例えば:

- (NSString *)hello:(NSString *)name {
    return [[NSString alloc] initWithFormat:@"Hello, %@", name];
}

また

- (NSString *)hello:(NSString *)name {
    return [NSString stringWithFormat:@"Hello, %@", name];
//    return [@"Hello, " stringByAppendingString:name]; // even simpler
}

ほとんどの場合、ここでのパフォーマンスは問題にならないはずです。しかし、私はそれをより良い方法で行う習慣をつけたいと思っています.

まったく同じことを行う場合は、入力が短く読みやすい後者のオプションを好みます。

Xcode 4.2 では、ARC が何にコンパイルされるか、つまりretain、、、などをどこに置くかを確認する方法はありますか? この機能は、ARC に切り替える際に非常に役立ちます。このようなことについて考える必要がないことはわかっていますが、このような質問に対する答えを見つけるのに役立ちます.releaseautorelease

4

6 に答える 6

37

違いは微妙ですが、バージョンを選択する必要がありautoreleaseます。まず、コードがはるかに読みやすくなります。次に、最適化されたアセンブリの出力を調べると、autoreleaseバージョンの方がわずかに最適です。

autoreleaseバージョン、

- (NSString *)hello:(NSString *)name {
    return [NSString stringWithFormat:@"Hello, %@", name];
}

に変換します

"-[SGCAppDelegate hello:]":
    push    {r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    mov r3, r2
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_-(LPC0_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC0_1+4))
    add r1, pc
    add r0, pc
    mov r7, sp
    ldr r1, [r1]
    ldr r0, [r0]
    movw    r2, :lower16:(L__unnamed_cfstring_-(LPC0_2+4))
    movt    r2, :upper16:(L__unnamed_cfstring_-(LPC0_2+4))
    add r2, pc
    blx _objc_msgSend    ; stringWithFormat:
    pop {r7, pc}

[[alloc] init] バージョンは次のようになります。

"-[SGCAppDelegate hello:]":
    push    {r4, r5, r6, r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4))
    add r7, sp, #12
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    add r1, pc
    add r0, pc
    ldr r5, [r1]
    ldr r6, [r0]
    mov r0, r2
    blx _objc_retain    ; ARC retains the name string temporarily
    mov r1, r5
    mov r4, r0
    mov r0, r6
    blx _objc_msgSend   ; call to alloc
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4))
    mov r3, r4
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4))
    add r1, pc
    ldr r1, [r1]
    movw    r2, :lower16:(L__unnamed_cfstring_-(LPC1_3+4))
    movt    r2, :upper16:(L__unnamed_cfstring_-(LPC1_3+4))
    add r2, pc
    blx _objc_msgSend   ; call to initWithFormat:
    mov r5, r0
    mov r0, r4
    blx _objc_release   ; ARC releases the name string
    mov r0, r5
    pop.w   {r4, r5, r6, r7, lr}
    b.w _objc_autorelease

alloc予想どおり、 andinitWithFormat:メソッドを呼び出しているため、少し長くなります。特に興味深いのは、ここで ARC が次善のコードを生成していることです。これは、name文字列を保持し (_objc_retain への呼び出しで示されます)、後で への呼び出し後に解放されるためinitWithFormat:です。

次の例のように所有権修飾子を追加する__unsafe_unretainedと、コードは最適にレンダリングされます。 __unsafe_unretainedプリミティブ (コピー ポインター)代入セマンティクスを使用するようにコンパイラーに指示します。

- (NSString *)hello:(__unsafe_unretained NSString *)name {
    return [[NSString alloc] initWithFormat:@"Hello, %@", name];
}

次のように:

"-[SGCAppDelegate hello:]":
    push    {r4, r7, lr}
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4))
    add r7, sp, #4
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_2-(LPC1_0+4))
    movw    r0, :lower16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    movt    r0, :upper16:(L_OBJC_CLASSLIST_REFERENCES_$_-(LPC1_1+4))
    add r1, pc
    add r0, pc
    mov r4, r2
    ldr r1, [r1]
    ldr r0, [r0]
    blx _objc_msgSend
    movw    r1, :lower16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4))
    mov r3, r4
    movt    r1, :upper16:(L_OBJC_SELECTOR_REFERENCES_4-(LPC1_2+4))
    add r1, pc
    ldr r1, [r1]
    movw    r2, :lower16:(L__unnamed_cfstring_-(LPC1_3+4))
    movt    r2, :upper16:(L__unnamed_cfstring_-(LPC1_3+4))
    add r2, pc
    blx _objc_msgSend
    .loc    1 31 1
    pop.w   {r4, r7, lr}
    b.w _objc_autorelease
于 2011-08-09T06:22:02.667 に答える
10

[NSString stringWithFormat:]コードが少ないです。ただし、オブジェクトが自動解放プールに入る可能性があることに注意してください。そして、これは現在、ARC および -Os コンパイラーの最適化でも発生します。

現在、 のパフォーマンスは[[NSString alloc] initWithFormat:]、iOS (iOS 5.1.1 および Xcode 4.3.3 でテスト済み) と OS X (OS X 10.7.4 および Xcode 4.3.3 でテスト済み) の両方で優れています。@Pascal のサンプル コードを修正して、自動解放プールのドレイン時間を含めると、次の結果が得られました。

  • ARC の最適化は、オブジェクトが自動解放プールに入ることを妨げません。

  • 100 万個のオブジェクトでリリース プールをクリアする時間を含めると[[NSString alloc] initWithFormat:]、iPhone 4S で約 14%、OS X で約 8% 高速です。

  • ループの周りに @autoreleasepool があると、ループの最後ですべてのオブジェクトが解放され、大量のメモリが消費されます。

    iOS 5.1 の [[NSString alloc] initWithFormat:] ではなく [NSString stringWithFormat:] のメモリ スパイクを示すインストゥルメント

  • ループ内で @autoreleasepool を使用すると、メモリ スパイクを防ぐことができます。パフォーマンスはほぼ同じままですが、メモリ消費量は横ばいです。

于 2012-06-26T16:54:59.043 に答える
4

他の回答には同意しません。自動リリース バージョン (2 番目の例) が必ずしも優れているとは限りません。

自動リリース バージョンは、ARC 以前と同様に動作します。これは、割り当て、初期化、および自動解放を行います。つまり、次回自動解放プールが空になったときに自動解放されるように、オブジェクトへのポインターを保存する必要があります。そのオブジェクトへのポインターは、処理されるまで保持する必要があるため、これはわずかに多くのメモリを使用します。また、オブジェクトは、すぐに解放された場合よりも長くくっつきます。これは、ループ内でこれを何度も呼び出している場合に問題になる可能性があるため、自動解放プールが空になる可能性はありません。これにより、メモリが不足する可能性があります。

最初の例は、ARC 以前とは動作が異なります。ARC では、コンパイラが「リリース」を挿入するようになりました (2 番目の例のような自動リリースではありません)。これは、メモリが割り当てられているブロックの最後で行われます。通常、これは呼び出される関数の最後にあります。あなたの例では、アセンブリを見ると、オブジェクトが実際に自動解放されているように見えます。これは、関数がどこに戻るのか、つまりブロックの終わりがどこにあるのかをコンパイラが認識していないことが原因である可能性があります。ブロックの最後にコンパイラによってリリースが追加されるほとんどの場合、alloc/init メソッドを使用すると、少なくともメモリ使用量に関しては、ARC 以前と同様にパフォーマンスが向上します。

于 2012-01-18T20:26:11.123 に答える
3

これは簡単にテストできるものであり、実際、便利なコンストラクターの方が「速い」ようです。テスト コードにエラーがない限り、以下を参照してください。

アウトプット(100万回の施工時間)

Alloc/init:   842.549473 millisec
Convenience:  741.611933 millisec
Alloc/init:   799.667462 millisec
Convenience:  741.814478 millisec
Alloc/init:   821.125221 millisec
Convenience:  741.376502 millisec
Alloc/init:   811.214693 millisec
Convenience:  795.786457 millisec

脚本

#import <Foundation/Foundation.h>
#import <mach/mach_time.h>

int main (int argc, const char * argv[])
{

    @autoreleasepool {
        NSUInteger runs = 4;

        mach_timebase_info_data_t timebase;
        mach_timebase_info(&timebase);
        double ticksToNanoseconds = (double)timebase.numer / timebase.denom;

        NSString *format = @"Hello %@";
        NSString *world = @"World";

        NSUInteger t = 0;
        for (; t < 2*runs; t++) {
            uint64_t start = mach_absolute_time();
            NSUInteger i = 0;
            for (; i < 1000000; i++) {
                if (0 == t % 2) {       // alloc/init
                    NSString *string = [[NSString alloc] initWithFormat:format, world];
                }
                else {                  // convenience
                    NSString *string = [NSString stringWithFormat:format, world];
                }
            }
            uint64_t run = mach_absolute_time() - start;
            double runTime = run * ticksToNanoseconds;

            if (0 == t % 2) {
                NSLog(@"Alloc/init:   %.6f millisec", runTime / 1000000);
            }
            else {
                NSLog(@"Convenience:  %.6f millisec", runTime / 1000000);
            }
        }
    }
    return 0;
}
于 2011-11-04T02:40:38.893 に答える
1

2 つのパフォーマンスを比較することは、いくつかの理由から少し議論の余地があります。まず、Clang が進化し、新しい最適化がコンパイラに追加されると、2 つのパフォーマンス特性が変わる可能性があります。第 2 に、あちこちでいくつかの命令をスキップすることの利点は、せいぜい疑わしいものです。アプリのパフォーマンスは、メソッドの境界を越えて考慮する必要があります。1 つのメソッドを分解することは、だまされる可能性があります。

于 2014-05-02T16:22:04.223 に答える
0

stringWithFormat: 実装は実際には最初のバージョンと同じように実装されていると思います。つまり、何も変更しないでください。いずれにせよ、違いがあるとすれば、おそらく 2 番目のバージョンが遅くなることはないように思われます。最後に、私の意見では、2 番目のバージョンの方が少し読みやすいので、それを使用します。

于 2011-08-08T21:13:53.477 に答える