36

質問は教育目的で行われます。

2つのオブジェクト(たとえば2つのスレッド)間で30〜50以上の信号と​​スロットのペアを使用すると、アプリケーションのパフォーマンス、実行時間、または応答時間に影響しますか?

4

2 に答える 2

68

まず第一に、おそらくQThreadsにスロットを入れてはいけません。runQThreadsは、メソッドとプライベートメソッド(シグナルではありません!)を再実装する以外の方法で派生することを意図したものではありません。

QThreadは、概念的にはスレッドコントローラーであり、スレッド自体ではありません。ほとんどの場合、QObjectを処理する必要があります。スレッドを開始してから、オブジェクトインスタンスをそのスレッドに移動します。これが、スレッドでスロットを正しく機能させる唯一の方法です。スレッドインスタンス(QObjectから派生しています!)をスレッドに移動することは、ハックであり、悪いスタイルです情報に基づいていないフォーラムの投稿が別のことを言っているにもかかわらず、それをしないでください。

残りの質問については、シグナルスロット呼び出しは何も見つけたり検証したりする必要はありません。「ロケーション」と「検証」は、接続が確立されたときに行われます。通話時に行われる主な手順は次のとおりです。

  1. プールからのシグナルスロットミューテックスのロック。

  2. 接続リストを繰り返し処理します。

  3. 直接呼び出しまたはキューに入れられた呼び出しのいずれかを使用して呼び出しを実行します。

共通コスト

シグナルスロットの呼び出しは、mocによって生成されたシグナルの実装では常に直接呼び出しとして開始されます。信号の引数へのポインタの配列がスタック上に構築されます。引数はコピーされません。

次に、信号が呼び出さQMetaObject::activateれ、接続リストmutexが取得され、接続されたスロットのリストが繰り返されて、各スロットが呼び出されます。

直接接続

QObject::qt_static_metacallそこではあまり行われていません。スロットは、接続が確立されたときに取得された直接呼び出しによって、または接続のセットアップに使用されたQObject::qt_metacall場合に呼び出されます。QMetaObject::connect後者は、信号とスロットの動的な作成を可能にします。

キュー接続

呼び出しはイベントキューに格納され、シグナルが返される必要があるため、引数はマーシャリングしてコピーする必要があります。これは、ポインタの配列をコピーに割り当て、ヒープ上の各引数をコピー構成することによって行われます。それを行うためのコードは、実際には飾り気のない単純な古いCです。

呼び出しのキューイングは内で行われますqueued_activate。ここでコピー構築が行われます。

キューに入れられた呼び出しのオーバーヘッドは、常に少なくとも1つのヒープ割り当てですQMetaCallEvent。呼び出しに引数がある場合は、argumentsへのポインター配列が割り当てられ、引数ごとに追加の割り当てが行われます。n引数付きの呼び出しの場合、C式として指定されるコストは(n ? 2+n : 1)割り当てです。呼び出しをブロックするための戻り値は、引数としてのcounterです。間違いなく、Qtのこの側面は、すべてに対して1つの割り当てに最適化できますが、実際には、些細なメソッドを呼び出している場合にのみ問題になります。

ベンチマーク結果

直接の(キューに入れられていない)シグナルスロットコールでさえ、測定可能なオーバーヘッドがありますが、戦闘を選択する必要があります。コードとパフォーマンスの設計のしやすさ。最終的なアプリケーションのパフォーマンスを測定し、ボトルネックを特定しますか?そうした場合、実際のアプリケーションでは、信号スロットのオーバーヘッドは何の役割も果たさないことに気付くでしょう。

シグナルスロットメカニズムに大きなオーバーヘッドがあるのは、些細な関数を呼び出す場合だけです。trivialたとえば、以下のコードでスロットを呼び出す場合。これは完全なスタンドアロンベンチマークなので、自由に実行して自分の目で確かめてください。私のマシンでの結果は次のとおりです。

Warming up the caches...
trivial direct call took 3ms
nonTrivial direct call took 376ms
trivial direct signal-slot call took 158ms, 5166% longer than direct call.
nonTrivial direct signal-slot call took 548ms, 45% longer than direct call.
trivial queued signal-slot call took 2474ms, 1465% longer than direct signal-slot and 82366% longer than direct call.
nonTrivial queued signal-slot call took 2474ms, 416% longer than direct signal-slot and 653% longer than direct call.

おそらく、文字列の連結は非常に高速であることに注意する必要があります:)

関数ポインタを介して呼び出しを行っていることに注意してください。これは、コンパイラが加算関数への直接呼び出しを最適化するのを防ぐためです。

//main.cpp
#include <cstdio>
#include <QCoreApplication>
#include <QObject>
#include <QTimer>
#include <QElapsedTimer>
#include <QTextStream>

static const int n = 1000000;

class Test : public QObject
{
    Q_OBJECT
public slots:
    void trivial(int*, int, int);
    void nonTrivial(QString*, const QString&, const QString&);
signals:
    void trivialSignalD(int*, int, int);
    void nonTrivialSignalD(QString*, const QString&, const QString &);
    void trivialSignalQ(int*, int, int);
    void nonTrivialSignalQ(QString*, const QString&, const QString &);
private slots:
    void run();
private:
    void benchmark(bool timed);
    void testTrivial(void (Test::*)(int*,int,int));
    void testNonTrivial(void (Test::*)(QString*,const QString&, const QString&));
public:
    Test();
};

Test::Test()
{
    connect(this, SIGNAL(trivialSignalD(int*,int,int)),
            SLOT(trivial(int*,int,int)), Qt::DirectConnection);
    connect(this, SIGNAL(nonTrivialSignalD(QString*,QString,QString)),
            SLOT(nonTrivial(QString*,QString,QString)), Qt::DirectConnection);
    connect(this, SIGNAL(trivialSignalQ(int*,int,int)),
            SLOT(trivial(int*,int,int)), Qt::QueuedConnection);
    connect(this, SIGNAL(nonTrivialSignalQ(QString*,QString,QString)),
            SLOT(nonTrivial(QString*,QString,QString)), Qt::QueuedConnection);
    QTimer::singleShot(100, this, SLOT(run()));
}

void Test::run()
{
    // warm up the caches
    benchmark(false);
    // do the benchmark
    benchmark(true);
}

void Test::trivial(int * c, int a, int b)
{
    *c = a + b;
}

void Test::nonTrivial(QString * c, const QString & a, const QString & b)
{
    *c = a + b;
}

void Test::testTrivial(void (Test::* method)(int*,int,int))
{
    static int c;
    int a = 1, b = 2;
    for (int i = 0; i < n; ++i) {
        (this->*method)(&c, a, b);
    }
}

void Test::testNonTrivial(void (Test::* method)(QString*, const QString&, const QString&))
{
    static QString c;
    QString a(500, 'a');
    QString b(500, 'b');
    for (int i = 0; i < n; ++i) {
        (this->*method)(&c, a, b);
    }
}

static int pct(int a, int b)
{
    return (100.0*a/b) - 100.0;
}

void Test::benchmark(bool timed)
{
    const QEventLoop::ProcessEventsFlags evFlags =
            QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers;
    QTextStream out(stdout);
    QElapsedTimer timer;
    quint64 t, nt, td, ntd, ts, nts;

    if (!timed) out << "Warming up the caches..." << endl;

    timer.start();
    testTrivial(&Test::trivial);
    t = timer.elapsed();
    if (timed) out << "trivial direct call took " << t << "ms" << endl;

    timer.start();
    testNonTrivial(&Test::nonTrivial);
    nt = timer.elapsed();
    if (timed) out << "nonTrivial direct call took " << nt << "ms" << endl;

    QCoreApplication::processEvents(evFlags);

    timer.start();
    testTrivial(&Test::trivialSignalD);
    QCoreApplication::processEvents(evFlags);
    td = timer.elapsed();
    if (timed) {
        out << "trivial direct signal-slot call took " << td << "ms, "
               << pct(td, t) << "% longer than direct call." << endl;
    }

    timer.start();
    testNonTrivial(&Test::nonTrivialSignalD);
    QCoreApplication::processEvents(evFlags);
    ntd = timer.elapsed();
    if (timed) {
        out << "nonTrivial direct signal-slot call took " << ntd << "ms, "
               << pct(ntd, nt) << "% longer than direct call." << endl;
    }

    timer.start();
    testTrivial(&Test::trivialSignalQ);
    QCoreApplication::processEvents(evFlags);
    ts = timer.elapsed();
    if (timed) {
        out << "trivial queued signal-slot call took " << ts << "ms, "
               << pct(ts, td) << "% longer than direct signal-slot and "
               << pct(ts, t) << "% longer than direct call." << endl;
    }

    timer.start();
    testNonTrivial(&Test::nonTrivialSignalQ);
    QCoreApplication::processEvents(evFlags);
    nts = timer.elapsed();
    if (timed) {
        out << "nonTrivial queued signal-slot call took " << nts << "ms, "
               << pct(nts, ntd) << "% longer than direct signal-slot and "
               << pct(nts, nt) << "% longer than direct call." << endl;
    }
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Test t;
    return a.exec();
}

#include "main.moc"
于 2012-05-31T18:41:57.333 に答える
4

もちろん、これらはアプリケーションのパフォーマンスに影響を与えます。これは主に、接続オブジェクトの検索+スロットオブジェクトの状態nの検証に費やされる時間のためです。ただし、信号とスロットのメカニズムの単純さと柔軟性は、オーバーヘッドに見合うだけの価値があります。Plus one of the major advantage of signal-slot mechanism is they are type=safe allowing communication between objects, irrespective of type of object unlike callbacks.

コールバックと比較すると、シグナルとスロットは柔軟性が高いためわずかに遅くなりますが、実際のアプリケーションとの違いはわずかです。一般に、一部のスロットに接続されている信号を発信する場合、非仮想関数呼び出しを使用してレシーバーを直接呼び出すよりも約10倍遅くなります。これは、接続オブジェクトを特定し、すべての接続を安全に反復し(つまり、放出中に後続のレシーバーが破棄されていないことを確認し)、一般的な方法でパラメーターをマーシャリングするために必要なオーバーヘッドです。10回の非仮想関数呼び出しは多くのように聞こえるかもしれませんが、たとえば、新規操作や削除操作よりもはるかにオーバーヘッドが少なくなります。舞台裏で新規または削除が必要な文字列、ベクトル、またはリスト操作を実行するとすぐに、

出典:シグナルとスロット

于 2012-05-31T17:29:45.990 に答える