コグノスケ


2017年 11月 5日

Android のメディア再生処理

たまには Android の話でも。Android のメディア再生のデコード完了から出画までを見てみました。

Media | Android Open Source Project 辺りにあるように、Android は libstagefright にメディアの処理を任せています。

図からはちょっと読み取りづらいですが、libstagefright は動画、音声のデコードに OpenMAX という API を用います。図だと OMX Core と書かれている部分です。

OpenMAX ざっくり紹介

OpenMAX の各種デコーダ(※)は「コンポーネント」と呼ばれる部品になっています。

OpenMAX ではデータの入力は EmptyThisBuffer と呼びます。データの入力は非同期に行うことができます。コンポーネントは入力を処理し終えたら完了通知をコールバック(EmptyBufferDone)する仕組みになっています。

データの出力は FillThisBuffer と呼びます。出力も非同期に行うことができます。コンポーネントはデータ出力の完了通知(FillBufferDone)をする仕組みになっています。

libstagefright は OpenMAX のコンポーネントに対して、下記の処理を行います。他にも設定、フラッシュ、などややこしい処理がありますが、省略。

入力側はこんな感じです。

  • 圧縮データが入ったバッファを EmptyThisBuffer でコンポーネントに渡す
  • (コンポーネント内でバッファのデータが処理される)
  • 空になったバッファが EmptyBufferDone で返ってくる

出力側もほぼ同じです。

  • 空のバッファを FillThisBuffer でコンポーネントに渡す
  • (コンポーネント内でバッファにデコード済みデータが詰め込まれる)
  • デコード済みデータが入ったバッファが FillBufferDone で返ってくる

バッファが 2 つある場合も基本的には同じです。OpenMAX の特徴はバッファ 1 とバッファ 2 がお互いを気にしなくて良いことです。バッファ 2 が返ってきていようが返ってきていまいが、バッファ 1 はコンポーネントに渡して構いません。

例えばバッファ 2 にすごく時間が掛かって、こんな順になっても構いません(コロンの右側はコンポーネントに渡したが返ってきていないバッファの一覧)。

  • start: なし
  • FillThisBuffer 1: 1
  • FillThisBuffer 2: 1, 2
  • FillBufferDone 1: 2
  • FillThisBuffer 1: 1, 2
  • FillBufferDone 1: 2
  • FillThisBuffer 1: 1, 2
  • FillBufferDone 2: 1
  • FillBufferDone 1: なし

特にデコーダの場合は、この例のように渡した順番と返ってくる順番が違う場合がほとんどです。

(※)OpenMAX の規格が定義するコンポーネントの機能は、デコーダだけではありません。しかし Android はデコーダコンポーネントしか使いません

編集者: すずき(更新: 2017年 11月 23日 19:45)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 6日

Android メディア処理

昨日(2017年 11月 5日の日記参照)の続きです。

OpenMAX の解説をしていると日が暮れるのでやめます。とにかくデコードされた画素データは FillBufferDone で返ってくることがわかっていれば、コードを追いかけられるはずです。

見ているコードは Android 7.1 です。タグで言えば android-7.1.2_r33 辺りです。

デコード完了のお知らせ

FillBufferDone はコールバックであることは説明しました。OpenMAX の規格では、コンポーネントがコールバックする関数は、コンポーネントを生成する際に指定します。コールバックされる関数を探すには、コンポーネントを生成していそうな個所を探せばわかるはずです。

OpenMAX コンポーネント生成とコールバックの指定

//android/frameworks/av/media/libstagefright/omx/OMX.cpp

status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer,
        sp<IBinder> *nodeBinder, node_id *node) {

...
    
    OMXNodeInstance *instance = new OMXNodeInstance(this, observer, name); //★★1番目の引数が owner なので、this つまりこのオブジェクトが指定される★★

    OMX_COMPONENTTYPE *handle;
    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle); //★★2番目の引数 kCallbacks がコールバック関数の指定。3番目の引数 instance が FillBufferDone の pAppData に渡される★★


//android/frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp

// static
OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {
    &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};

かなり端折ってますが、FillBufferDone のコールバック関数には OMXNodeInstance::OnFillBufferDone を指定しているようです。従ってデコードが終わると、画素データが入ったバッファが OMXNodeInstance::OnFillBufferDone 関数に渡されます。

デコード完了のコールバック処理

//android/frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp
// static
OMX_ERRORTYPE OMXNodeInstance::OnFillBufferDone(
        OMX_IN OMX_HANDLETYPE /* hComponent */,
        OMX_IN OMX_PTR pAppData,
        OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {

...

    OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); //★★makeComponentInstance の 3番目の引数に渡した値★★
    if (instance->mDying) {
        return OMX_ErrorNone;
    }
    int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
    return instance->owner()->OnFillBufferDone(instance->nodeID(),
            instance->findBufferID(pBuffer), pBuffer, fenceFd); //★★owner は OMX 型のオブジェクトなので OMX::OnFillBuffer を見る★★
}


//android/frameworks/av/media/libstagefright/omx/OMX.cpp

OMX_ERRORTYPE OMX::OnFillBufferDone(
        node_id node, buffer_id buffer, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer, int fenceFd) {
    ALOGV("OnFillBufferDone buffer=%p", pBuffer);

    omx_message msg;
    msg.type = omx_message::FILL_BUFFER_DONE;
    msg.node = node;
    msg.fenceFd = fenceFd;
    msg.u.extended_buffer_data.buffer = buffer;
    msg.u.extended_buffer_data.range_offset = pBuffer->nOffset;
    msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen;
    msg.u.extended_buffer_data.flags = pBuffer->nFlags;
    msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;

    findDispatcher(node)->post(msg); //★★post() とは何だろうか??★★

    return OMX_ErrorNone;
}

sp<OMX::CallbackDispatcher> OMX::findDispatcher(node_id node) {
    Mutex::Autolock autoLock(mLock);

    ssize_t index = mDispatchers.indexOfKey(node);

    return index < 0 ? NULL : mDispatchers.valueAt(index); //★★mDispatchers とは?★★
}

謎の関数 CallbackDispatcher::post() が出てきました。名前からするとメッセージパッシングを行うための関数ではないかと予想されます。この場所に限らず stagefright ではあらゆる場所でメッセージパッシングが使用されており、とても読みづらいです……。

メッセージ from OpenMAX

CallbackDispatcher というクラスが出てきましたので、見てみます。

メッセージの生成

//android/frameworks/av/media/libstagefright/include/OMX.h

class OMX : public BnOMX,
            public IBinder::DeathRecipient {

...

    KeyedVector<node_id, sp<CallbackDispatcher> > mDispatchers; //★★mDispatchers の定義★★


//android/frameworks/av/media/libstagefright/omx/OMX.cpp

struct OMX::CallbackDispatcher : public RefBase {
    CallbackDispatcher(OMXNodeInstance *owner);

    // Posts |msg| to the listener's queue. If |realTime| is true, the listener thread is notified
    // that a new message is available on the queue. Otherwise, the message stays on the queue, but
    // the listener is not notified of it. It will process this message when a subsequent message
    // is posted with |realTime| set to true.
    void post(const omx_message &msg, bool realTime = true);
...

private:
...
    std::list<omx_message> mQueue;


void OMX::CallbackDispatcher::post(const omx_message &msg, bool realTime) {
    Mutex::Autolock autoLock(mLock);

    mQueue.push_back(msg); //★★メッセージをキューに追加★★
    if (realTime) {
        mQueueChanged.signal();
    }
}

引数の node_id node から、適切な CallbackDispatcher を探して、内部キュー mQueue にメッセージを追加しています。mQueue を手掛かりにメッセージを処理する側を探すと、どうやら CallbackDispatcherThread が処理しているようです。

メッセージの消費

//android/frameworks/av/media/libstagefright/omx/OMX.cpp

bool OMX::CallbackDispatcherThread::threadLoop() {
    return mDispatcher->loop();
}

bool OMX::CallbackDispatcher::loop() {
    for (;;) {
        std::list<omx_message> messages;

        {
            Mutex::Autolock autoLock(mLock);
            while (!mDone && mQueue.empty()) {
                mQueueChanged.wait(mLock);
            }

            if (mDone) {
                break;
            }

            messages.swap(mQueue); //★★mQueue のロック時間を短くするため、別のリストに全てのメッセージを移動させる★★
        }

        dispatch(messages); //★★メッセージ処理★★
    }

    return false;
}

void OMX::CallbackDispatcher::dispatch(std::list<omx_message> &messages) {
    if (mOwner == NULL) {
        ALOGV("Would have dispatched a message to a node that's already gone.");
        return;
    }
    mOwner->onMessages(messages); //★★メッセージ送信先の mOwner とは?★★
}

OMX::CallbackDispatcher::CallbackDispatcher(OMXNodeInstance *owner)
    : mOwner(owner), //★★CallbackDispatcher の生成時に渡された引数で初期化されている★★
      mDone(false) {
    mThread = new CallbackDispatcherThread(this);
    mThread->run("OMXCallbackDisp", ANDROID_PRIORITY_FOREGROUND);
}

ここまででわかったことは、

  • コンポーネントが OMXNodeInstance::OnFillBufferDone をコールバックする
  • OMX::OnFillBufferDone がメッセージを送信する
  • メッセージの行き先は OMX::mDispatchers に登録されている CallbackDispatcher を new するときに渡した引数(mOwner)

困ったことに、肝心のメッセージがどこに行くか?がいまだに不明です。mOwner とはどこで指定されているのでしょう?

メッセージはどこへ行く

OMX::mDispatchers を操作している箇所を探すと、1箇所見つかります。先程も出てきた OMX::allocateNode() です。

メッセージは誰に届くのか

//android/frameworks/av/media/libstagefright/omx/OMX.cpp

status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer,
        sp<IBinder> *nodeBinder, node_id *node) {

...

    OMXNodeInstance *instance = new OMXNodeInstance(this, observer, name);

    OMX_COMPONENTTYPE *handle;
    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle); //★★3番目の引数、つまり instance が FillBufferDone の pAppData に渡される★★

    if (err != OMX_ErrorNone) {
        ALOGE("FAILED to allocate omx component '%s' err=%s(%#x)", name, asString(err), err);

        instance->onGetHandleFailed();

        return StatusFromOMXError(err);
    }

    *node = makeNodeID_l(instance);
    mDispatchers.add(*node, new CallbackDispatcher(instance)); //★★メッセージの送信先を登録する★★

どうやら OMXNodeInstance にメッセージを送っているようです。従って mOwner->onMessages(messages) はここに辿り着きます。

メッセージが届きました?

void OMXNodeInstance::onMessages(std::list<omx_message> &messages) {
    for (std::list<omx_message>::iterator it = messages.begin(); it != messages.end(); ) {
        if (handleMessage(*it)) {
            messages.erase(it++); //★★デコードに付随する情報をメッセージに載せる★★
        } else {
            ++it;
        }
    }

    if (!messages.empty()) {
        mObserver->onMessages(messages); //★★mObserver とは?★★
    }
}

ここで終わりかと思いきや、まだです。mObserver とは何者でしょうか?メッセージの冒険は続きます。

編集者: すずき(更新: 2017年 11月 23日 20:55)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 7日

Android メディア処理

昨日(2017年 11月 6日の日記参照)の続きです。

メッセージの宛先

メッセージが OMXNodeInstance::onMessages() 関数にたどり着き、次に OMXNodeInstance::mObserver に渡されていることはわかりましたが、これは一体何者でしょうか?

observer とは?

//android/frameworks/av/media/libstagefright/include/OMXNodeInstance.h

struct OMXNodeInstance {
...
private:
...
    sp<IOMXObserver> mObserver;


//android/frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp

OMXNodeInstance::OMXNodeInstance(
        OMX *owner, const sp<IOMXObserver> &observer, const char *name)
    : mOwner(owner),
      mNodeID(0),
      mHandle(NULL),
      mObserver(observer), //★★コンストラクタの 2番目の引数 observer で初期化している★★
      mDying(false),
      mSailed(false),
      mQueriedProhibitedExtensions(false),
      mBufferIDCount(0)
{


//android/frameworks/av/media/libstagefright/omx/OMX.cpp

status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer,
        sp<IBinder> *nodeBinder, node_id *node) {

...

    OMXNodeInstance *instance = new OMXNodeInstance(this, observer, name); //★★allocateNode の 2番目の引数 observer を渡している★★

残念ながら allocateNode() の引数がわからないため、observer に何が指定されているかわかりません。

allocateNode の observer にたどり着くのは大変

//android/frameworks/av/media/libstagefright/ACodec.cpp

bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {

...

    OMXClient client; //★★binder のクライアント★★
    if (client.connect() != OK) { //★★デコーダは別プロセスで実行されているので、接続する★★
        mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
        return false;
    }

...

    sp<IOMX> omx = client.interface(); //★★binder を使って通信するためのインタフェース★★


//android/frameworks/av/media/libstagefright/OMXClient.cpp

class OMXClient {
public:
    OMXClient();

    status_t connect();
    void disconnect();

    sp<IOMX> interface() {
        return mOMX; //★★インタフェースはこれ★★
    }


//android/frameworks/av/media/libstagefright/OMXClient.cpp

status_t OMXClient::connect() {
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> playerbinder = sm->getService(String16("media.player"));
    sp<IMediaPlayerService> mediaservice = interface_cast<IMediaPlayerService>(playerbinder);

...

    sp<IOMX> mediaServerOMX = mediaservice->getOMX();

...

    sp<IBinder> codecbinder = sm->getService(String16("media.codec"));
    sp<IMediaCodecService> codecservice = interface_cast<IMediaCodecService>(codecbinder);

...

    sp<IOMX> mediaCodecOMX = codecservice->getOMX();

...

    mOMX = new MuxOMX(mediaServerOMX, mediaCodecOMX); //★★インタフェースはここで設定している★★

    return OK;
}

なかなか複雑ですね。このインタフェースとやらの実体は MuxOMX だと思われます。

allocateNode の observer

//android/frameworks/av/media/libstagefright/ACodec.cpp

bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {

...

    sp<IOMX> omx = client.interface(); //★★MuxOMX のオブジェクトのはず★★

...

    sp<CodecObserver> observer = new CodecObserver; //★★たぶんこれが observer★★
    IOMX::node_id node = 0;

    status_t err = NAME_NOT_FOUND;
    for (size_t matchIndex = 0; matchIndex < matchingCodecs.size();
            ++matchIndex) {
        componentName = matchingCodecs[matchIndex];
        quirks = MediaCodecList::getQuirksFor(componentName.c_str());

        pid_t tid = gettid();
        int prevPriority = androidGetThreadPriority(tid);
        androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
        err = omx->allocateNode(componentName.c_str(), observer, &mCodec->mNodeBinder, &node); //★★ここで observer を MuxOMX::allocateNode に渡す★★


//android/frameworks/av/media/libstagefright/OMXClient.cpp

status_t MuxOMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer,
        sp<IBinder> *nodeBinder,
        node_id *node) {

...

    sp<IOMX> omx;

    node_location loc = getPreferredCodecLocation(name);
    if (loc == CODECPROCESS) {
        omx = mMediaCodecOMX;
    } else if (loc == MEDIAPROCESS) {
        omx = mMediaServerOMX;
    } else {
        if (mLocalOMX == NULL) {
            mLocalOMX = new OMX;
        }
        omx = mLocalOMX;
    }

    status_t err = omx->allocateNode(name, observer, nodeBinder, node); //★★OMX::allocateNode() などに渡す★★
    ALOGV("allocated node_id %x on %s OMX", *node, omx == mMediaCodecOMX ? "codecprocess" :
            omx == mMediaServerOMX ? "mediaserver" : "local");

突然、ここで三択(mMediaCodecOMX と mMediaServerOMX と mLocalOMX)になりますが、いずれの選択肢を選んでも、渡す observer は変わらず CodecObserver のはずです。それさえわかれば、とりあえず OK です。

編集者: すずき(更新: 2017年 11月 23日 21:38)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 8日

Android メディア処理

昨日(2017年 11月 7日の日記参照)の続きです。

どうも Android のメッセージシステムのたらい回しが激しすぎて、話が一向に進みません。本来見たかった道をざっくりまとめておくと、

  • デコード終わり
  • コールバック OMXNodeInstance::OnFillBufferDone()
  • instance->owner()->OnFillBufferDone() → OMX::OnFillBufferDone()
  • OMX::CallbackDispatcher::post()

これが 2017年 11月 6日の日記の前半部分です。post() によってメッセージがキューに追加されます。

  • OMX::CallbackDispatcher::loop()
  • OMX::CallbackDispatcher::dispatch()
  • mOwner->onMessages() → OMXNodeInstance::onMessages()
  • mObserver->onMessages() → ?

これが 2017年 11月 6日の日記の後半部分です。キューに追加されたメッセージは別スレッドで処理され、mObserver なるものに渡されていました。

  • mObserver->onMessages() → CodecObserver::onMessages()

そして 2017年 11月 7日の日記を丸々使い、OMXNodeInstance::mObserver の正体が CodecObserver だと思われるところまで来ました。

やっと来た observer

//android/frameworks/av/media/libstagefright/ACodec.cpp

struct CodecObserver : public BnOMXObserver {

...

    // from IOMXObserver
    virtual void onMessages(const std::list<omx_message> &messages) {
...

        sp<AMessage> notify = mNotify->dup();
        bool first = true;
        sp<MessageList> msgList = new MessageList();
        for (std::list<omx_message>::const_iterator it = messages.cbegin();
              it != messages.cend(); ++it) {
            const omx_message &omx_msg = *it;
            if (first) {
                notify->setInt32("node", omx_msg.node);
                first = false;
            }

            sp<AMessage> msg = new AMessage;
            //★★omx_msg.type は OMX::OnFillBufferDone() にて FILL_BUFFER_DONE に設定★★
            msg->setInt32("type", omx_msg.type);
            switch (omx_msg.type) {
...
                case omx_message::FILL_BUFFER_DONE:
                {
                    //★★omx_message から AMessage に変換している★★
                    msg->setInt32(
                            "buffer", omx_msg.u.extended_buffer_data.buffer);
                    msg->setInt32(
                            "range_offset",
                            omx_msg.u.extended_buffer_data.range_offset);
                    msg->setInt32(
                            "range_length",
                            omx_msg.u.extended_buffer_data.range_length);
                    msg->setInt32(
                            "flags",
                            omx_msg.u.extended_buffer_data.flags);
                    msg->setInt64(
                            "timestamp",
                            omx_msg.u.extended_buffer_data.timestamp);
                    msg->setInt32(
                            "fence_fd", omx_msg.fenceFd);
                    break;
                }
...
            }
            msgList->getList().push_back(msg);
        }
        notify->setObject("messages", msgList);
        notify->post(); //★★notify とは??★★
    }

また変なものが出てきました。notify = mNotify->dup() なので、次に mNotify が何者かを見ていきます。

今度は notify

//android/frameworks/av/media/libstagefright/ACodec.cpp

struct CodecObserver : public BnOMXObserver {
...

    void setNotificationMessage(const sp<AMessage> &msg) {
        mNotify = msg;
    }


bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {

...

    sp<CodecObserver> observer = new CodecObserver;
    IOMX::node_id node = 0;

...

    status_t err = NAME_NOT_FOUND;
    for (size_t matchIndex = 0; matchIndex < matchingCodecs.size();
            ++matchIndex) {
        componentName = matchingCodecs[matchIndex];
        quirks = MediaCodecList::getQuirksFor(componentName.c_str());

        pid_t tid = gettid();
        int prevPriority = androidGetThreadPriority(tid);
        androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
        err = omx->allocateNode(componentName.c_str(), observer, &mCodec->mNodeBinder, &node); //★★11月 7日の日記参照★★

...

    notify = new AMessage(kWhatOMXMessageList, mCodec);
    observer->setNotificationMessage(notify); //★★ここで設定している★★

従って mNotify は AMessage(kWhatOMXMessageList, mCodec) です。dup() は複製しているだけでしょうから、notify->post() は AMessage::post() が呼ばれるのでしょう。

今度は AMessage

//android/frameworks/av/media/libstagefright/foundation/AMessage.cpp

status_t AMessage::post(int64_t delayUs) {
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
        return -ENOENT;
    }

    looper->post(this, delayUs); //★★たらい回し再び、mLooper とは?★★
    return OK;
}

...

AMessage::AMessage(uint32_t what, const sp<const AHandler> &handler)
    : mWhat(what),
      mNumItems(0) {
    setTarget(handler); //★★mLooper はここから設定★★
}

...

void AMessage::setTarget(const sp<const AHandler> &handler) {
    if (handler == NULL) {
        mTarget = 0;
        mHandler.clear();
        mLooper.clear();
    } else {
        mTarget = handler->id();
        mHandler = handler->getHandler();
        mLooper = handler->getLooper(); //★★mLooper は AMessage コンストラクタの 2番目の引数の getLooper() が返す値★★
    }
}

うーん、また訳の分からないものが出てきましたね…。

編集者: すずき(更新: 2017年 11月 23日 23:54)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 9日

Android メディア処理

昨日(2017年 11月 8日の日記参照)の続きです。

本来見たかった道をざっくりまとめておくと、

  • デコード終わり
  • コールバック OMXNodeInstance::OnFillBufferDone()
  • instance->owner()->OnFillBufferDone() → OMX::OnFillBufferDone()
  • OMX::CallbackDispatcher::post()

これが 2017年 11月 6日の日記の前半で分かった部分。

  • OMX::CallbackDispatcher::loop()
  • OMX::CallbackDispatcher::dispatch()
  • mOwner->onMessages() → OMXNodeInstance::onMessages()
  • mObserver->onMessages() → ?

これが 2017年 11月 6日の日記の後半で分かった部分。

  • mObserver->onMessages() → CodecObserver::onMessages()

これが 2017年 11月 7日の日記で分かった部分。

  • mObserver->onMessages() → CodecObserver::onMessages()
  • notify->post() → AMessage::post()
  • looper->post() → ?

これが 2017年 11月 8日の日記で分かった部分です。そのあとは looper とは何ぞや?という点を追いかけていましたが、まだわからない状態です。

  • looper->post() → ?
  • looper = AMessage::mLooper
  • mLooper = handler->getLooper()
  • handler は AMessage() の 2番目の引数
  • notify = mNotify = AMessage(kWhatOMXMessageList, mCodec) だから、handler = ACodec::BaseState::mCodec
  • looper->post() → mCodec->getLooper()->post() のはず

再開

肝心の ACodec::BaseState::mCodec に何が入っているのか?については UninitializedState を手掛かりに見ていきます。

mCodec

//android/frameworks/av/media/libstagefright/ACodec.cpp

struct ACodec::BaseState : public AState {
    BaseState(ACodec *codec, const sp<AState> &parentState = NULL);

...

    ACodec *mCodec; //★★これが知りたい★★


//★★UninitializedState を手掛かりに見てみる★★

struct ACodec::UninitializedState : public ACodec::BaseState {

...

ACodec::UninitializedState::UninitializedState(ACodec *codec)
    : BaseState(codec) { //★★BaseState に丸投げ★★
}


//★★BaseState を見てみる★★

struct ACodec::BaseState : public AState {
    BaseState(ACodec *codec, const sp<AState> &parentState = NULL);

...

ACodec::BaseState::BaseState(ACodec *codec, const sp<AState> &parentState)
    : AState(parentState),
      mCodec(codec) { //★★引数をそのまま設定しているだけ★★
}


//★★UninitializedState の生成個所を探す★★

ACodec::ACodec()
    : mQuirks(0),
...
      mDescribeHDRStaticInfoIndex((OMX_INDEXTYPE)0) {
    mUninitializedState = new UninitializedState(this); //★★this が指すものは ACodec★★
    mLoadedState = new LoadedState(this);

つまり ACodec::BaseState::mCodec は、UninitializeState を生成した ACodec です。もう一つの謎 getLooper() が何を返すのか?も見てみます。

getLooper

//android/frameworks/av/include/media/stagefright/foundation/AHandler.h

struct AHandler : public RefBase {

...

    wp<ALooper> getLooper() const {
        return mLooper; //★★mLooper を返すだけ★★
    }

...

    inline void setID(ALooper::handler_id id, wp<ALooper> looper) {
        mID = id;
        mLooper = looper; //★★mLooper は setID の引数そのまま★★
    }


//android/frameworks/av/include/media/libstagefright/foundation/ALooperRoster.cpp

ALooper::handler_id ALooperRoster::registerHandler(
        const sp<ALooper> looper, const sp<AHandler> &handler) {
    Mutex::Autolock autoLock(mLock);

    if (handler->id() != 0) {
        CHECK(!"A handler must only be registered once.");
        return INVALID_OPERATION;
    }

    HandlerInfo info;
    info.mLooper = looper;
    info.mHandler = handler;
    ALooper::handler_id handlerID = mNextHandlerID++;
    mHandlers.add(handlerID, info);

    handler->setID(handlerID, looper); //★★setID を呼んでいる個所はここだけ★★

    return handlerID;
}


//media/libstagefright/foundation/ALooper.cpp

ALooperRoster gLooperRoster;

...

ALooper::handler_id ALooper::registerHandler(const sp<AHandler> &handler) {
    return gLooperRoster.registerHandler(this, handler);
}

ALooper::registerHandler は ALooper を AHandler に登録する仕組み、AHandler::getLooper() は AHandler に登録された ALooper を返す仕組みのようです。取得 / 設定が一致しないのでややこしいです。設計を失敗したのかなあ?

例えば AHandler *hoge と ALooper *fuga があって fuga->registerHandler(hoge) としたならば、hoge->getLooper() は先ほど登録した fuga を返します。

  • looper->post() → ?
  • looper = AMessage::mLooper
  • mLooper = handler->getLooper()
  • handler は AMessage() の 2番目の引数
  • notify = mNotify = AMessage(kWhatOMXMessageList, mCodec) だから、handler = ACodec::BaseState::mCodec
  • looper->post() → mCodec->getLooper()->post() のはず
  • ACodec::BaseState::mCodec は、UninitializeState を生成した ACodec だから
  • looper->post() → ACodec::getLooper()->post() のはず

ちなみに ACodec は AHandler を継承しているので getLooper() 関数を持っています。

ここまで分かれば ALooper::registerHandler() を呼んでいる個所を見て、引数が ACodec オブジェクトであろう場所を見つければ、looper が指しているのが、どの ALooper なのか?がやっと判明します。

しかし registerHandler() の呼び出し箇所は非常に多くて、追いきれません。うーん、別のアプローチが必要でしょうか……?

編集者: すずき(更新: 2017年 11月 24日 00:38)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 11日

ポケモン GO

ポケモン GO のアプリはいつまで経ってもバグだらけです。新しく実装された機能(ジムバトル)は当然バグバグで、通信周りが弱くハングしまくります。

  • ジムで木の実投げるときにジムから離れると操作不能
  • 木の実を投げたときに対象のポケモンが別のプレーヤに倒されると操作不能
  • ジムバトルの開始時にハング、勝利時にもハング
  • ログイン画面で WiFi から 3G/4G に切り換えるとログイン不能
  • ポケモン捕獲時にハングする
  • キャラクターが真っ黒になる
  • 地図が一面海になる

操作不能になったり、ハングされたりするとアプリを再起動するしかないですが、ハイエンド機じゃないせいか起動も動作も遅くてイライラします。

1日 15分もやってないのにこの有様なので、もっと長時間遊んでいる人はイライラで憤死するんじゃなかろうか?

折角面白いのにアプリが残念すぎる……。

編集者: すずき(更新: 2017年 11月 19日 20:24)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 12日

Kindle Fire の変なフォントが直っていた

目次: Kindle - まとめリンク

いつのまにか Kindle がアップデートされており、フォントが変になる問題(2017年 10月 13日の日記参照)が直っていました。あとストアアプリのメニューがダブって表示される問題(2017年 10月 12日の日記参照)も直っていました。

直してくれてありがとう。やっぱりおかしいってわかってたんだね……。

編集者: すずき(更新: 2021年 12月 8日 04:01)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 13日

テレビの栄枯盛衰

一応、テレビ向けの SoC を作るお仕事をしていますので、たまに電器屋さんにテレビを見に行ってますが、どこに行ってもテレビのコーナーは年々狭くなっています。

土曜日に梅田ヨドバシに行きましたが、一時期は 3F をテレビが支配していたのに、今や 1/3 位です。ホームシアターを除いて純粋にテレビだけでカウントしたら、もっと狭いかもしれません。テレビを家電の 1つと見れば、フロアの 1/3 を占めているのは破格の待遇と言えますが、つい過去の栄光と比べてしまいます。

同じ階にはオーディオコーナーと、キャンプ用品コーナーがありました。テレビはオーディオコーナーと同じか、やや負けてるくらいの広さでしょうか?この先、テレビの面積が復活することは無いでしょうから、そのうちオーディオと合併して、オーディオ・ビジュアルコーナーになるんでしょう、たぶん。

レコーダーはどこ?

レコーダーは悲惨で棚 2つしかありませんでした。BD-R とか DVD-R みたいなメディアそのものを売っている棚の方が多いように見えますけど、バランスおかしくないです??

番組を録画する文化は日本特有らしく、もともとレコーダーは日本でしか流行っていません。海外でも販売していますが、プレーヤーの方が好まれるようです。頼みの日本がこの状態だと、そのうちレコーダーという製品は無くなるかもしれません。

プレーヤーは細々と続くと思います。とはいえ、黒物家電メーカーは全員ボロボロで、次世代の光ディスク規格を作るほどの元気は無いでしょう。BD を 8K 規格まで延命して、ネットにバトンタッチして終わりか、下手したら 4K で燃え尽きて終わりかもね……。

編集者: すずき(更新: 2017年 11月 19日 21:16)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 22日

アイス履歴

アイス履歴を 10個ほど増やしました(リンク)。これで 55種類かな。そろそろカウントが面倒になってきました…。

最近はパピコやモナカのような棒アイス以外にも手を出しているので、アイスの袋の増え方が激しくアップロードしきれていません。そのうち載せます。

夏、秋は果実系のさわやかなアイスがおいしい季節でしたが、冬は味濃い系が恋しくなります。個人的にまた発売してほしいなー、と思うアイスは、

  • 赤城乳業 ミルクレア スイーツ ラムレーズン
  • 明治 ゴールドライン フランボワーズ
  • ロッテ カスタードとろけるほろにがカラメルのプリンアイスバー

辺りですね。他のアイスもおいしいです。ぜひ見かけたら食べてみてください、と言いたいところですが、アイスは商品の入れ替わりが激しくて、すぐにお店から消えるんですよねえ……。

その反面、アイスはほぼ毎週と言って良いほど、新商品が出ていてマンネリとは無縁です。メーカーさんの努力は素晴らしいです。

編集者: すずき(更新: 2017年 11月 23日 03:43)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 24日

モナコイン

仮想通貨の勉強がてら、モナコイン(以外の仮想通貨にも対応してますが)のマイナー cpuminer-multi を見ていました。かなり最適化されていて、迂闊に SSE を使うと逆に遅くなるほどです。面白いです。

モナコインのハッシュアルゴリズムは Lyra2REv2 という名前の 256ビットハッシュ関数で、複数のハッシュ関数の組み合わせでできています。

  • blake
  • keccak
  • cubehash
  • LYRA2
  • skein
  • cubehash(2回目)
  • bmw

上から順に実行されます。先頭の blake への入力は 80バイトで、出力は 32バイト。1つ前のハッシュ関数の出力が、2番目以降のハッシュ関数の入力となります。LYRA2 だけパラメータが 2つ(salt と password)必要ですが、どちらも同じ値を指定していました。

Lyra2REv2 を CPU で演算する場合 CubeHash に一番時間が掛かります。2回実行されることを差し引いて考えても遅いです。見ていると最終ラウンドが 160回という設定になっていて、これが異常に遅いみたいです。

実装(= cpuminer-multi の最適化された実装)を見ると、このハッシュ関数は 4ワードと別の 4ワードをペアにして演算をします。ワーク領域は 32ワードありますので、同じ演算が 4回実行されます。いかにも SSE に向いていそうな処理ですが、4ワードの組み合わせ方が変わるので、SSE レジスタにうまくパッキングできません。

  • (EVENラウンド)
  • 加算★
  • 左ローテート
  • XOR★
  • 2ワードずらして加算
  • 左ローテート
  • 2ワードずらして XOR
  • (ODDラウンド)
  • 逆順で加算
  • 左ローテート
  • 逆順で XOR
  • 逆順 2ワードずらして加算
  • 左ローテート
  • 逆順 2ワードずらして XOR

試しに★の部分だけ、単純に SSE を使ったら、余計遅くなりました。切ない。どうも SSE レジスタからのロード/ストアで引っかかって遅くなっているようです。しかし SSE には左ローテート演算がないため、左ローテートの前に必ずストアしなければなりません。

EVEN + ODD のラウンドが 8回繰り返されますが、安易に unrolling しても(※)やはり遅くなります。unrolling した後のマシン語を見ると嫌になるくらい長いので、命令キャッシュのヒット率が落ちてるのかな?

うーん、難しいです……。

(※)私が何かしたわけではなく cpuminer-multi には unrolling するコンパイルオプションが用意されていて、それを使ってみただけです。CubeHash 以外のハッシュ関数も unrolling するかしないかを選べます。素敵な作りです。

CubeHash

そもそも CubeHash って何なのか全く知らないので調べてみました。Wikipedia の解説(リンク)がとても親切です。CubeHash は NIST のハッシュ関数コンペに応募されたものなのだとか。次の SHA なんちゃらに採用されるかもしれないですね。

CubeHash にはパラメータがあり、パラメータが違うと全く違うハッシュ関数になります。Lyra2REv2 で使用しているのはどれ、という情報が見当たらなかったのですが、cpuminer-multi の実装(初期ラウンドは不明ですが、1周 16ラウンド、ブロックサイズ 32ビット、最終 160ラウンド、ハッシュ長 256ビット)から推測するに CubeHash160+16/32+160-256 じゃないか?と思われます。長い名前だなあ……。

キューブは 4個あって、i, j の 2次元で指定されます。i, j は 0 か 1 の値しかとりません。
キューブは 8個のブロックから構成され k, l, m の 3次元で指定されます。k, l, m も 0 か 1 の値しか取りません。
ブロックは 32ビットです…、というより Lyra2REv2 の CubeHash の場合は 32ビット、と言った方が正しいですね。

従って、全体で 4 * 8 = 32個のブロックが存在します。Wikipedia の図では i, j, k, l, m という 5次元のアドレスで表現していますが、計算の際は i, j, k, l, m をくっつけて 2進数だと思って数値に変換します。

例えば、右下のキューブ(i = 1, j = 0)、右上の手前側ブロック(k = 1, l = 1, m = 0)だったら、ijklm = 10110 = 22 になりま…、はい?わかりづらい?


CubeHash とブロック番号 i, j 次元


CubeHash とブロック番号 k, l, m 次元

これでわかりやすい?

CubeHash の素朴な実装

Wikipedia に載っている実装をそのまま実装すれば良いです。と言われてやる人は居ませんから、自分でやってみます。

アルゴリズムだけ実装しても、結果を確かめる術がないので cpuminer-multi に組み込める形で実装します。sha3/sph_cubehash.c に SIXTEEN_ROUNDS というマクロがあって、CubeHash の 1周(16ラウンド)に相当しています。このマクロを改造して自作の実装を差し込みます。

CubeHash の素朴な実装、準備編

#if 1 //今から作る実装を無理やり有効にする

#define SIXTEEN_ROUNDS   do { \
		int j; \
		for (j = 0; j < 16; j ++) { \
			ROUND_ONE; \
		} \
	} while (0)

#elif SPH_CUBEHASH_UNROLL == 2 //#if を #elif に変えてしまう(SPH_CUBEHASH_UNROLL オプションを無視)

#define SIXTEEN_ROUNDS   do { \
		int j; \
		for (j = 0; j < 8; j ++) { \
			ROUND_EVEN; \
			ROUND_ODD; \
		} \
	} while (0)

次にラウンドの処理を書きます。Wikipedia を見ながら 10個の手順をそのまま書きます。

CubeHash の素朴な実装

void sw(uint32_t *a, uint32_t *b)
{
	uint32_t tmp = *b;
	*b = *a;
	*a = tmp;
}

#define ROUND_ONE    do { \
		int i; \
		uint32_t *b = (sc)->state; \
		/* STEP 1, 2 */ \
		for (i = 0; i < 16; i++) { \
			b[i + 16] += b[i]; \
			b[i] = ROTL32(b[i], 7); \
		} \
		/* STEP 3 */ \
		for (i = 0; i < 8; i++) { \
			sw(&b[i], &b[i + 8]); \
		} \
		/* STEP 4 */ \
		for (i = 0; i < 16; i++) \
			b[i] ^= b[i + 16]; \
		/* STEP 5 */ \
		for (i = 0; i < 4; i++) { \
			sw(&b[16 + i * 4], &b[18 + i * 4]); \
			sw(&b[17 + i * 4], &b[19 + i * 4]); \
		} \
		/* STEP 6, 7 */ \
		for (i = 0; i < 16; i++) { \
			b[i + 16] += b[i]; \
			b[i] = ROTL32(b[i], 11); \
		} \
		/* STEP 8 */ \
		for (i = 0; i < 4; i++) { \
			sw(&b[0 + i], &b[4 + i]); \
			sw(&b[8 + i], &b[12 + i]); \
		} \
		/* STEP 9 */ \
		for (i = 0; i < 16; i++) \
			b[i] ^= b[i + 16]; \
		/* STEP 10 */ \
		for (i = 0; i < 4; i++) { \
			sw(&b[16 + i * 4], &b[17 + i * 4]); \
			sw(&b[18 + i * 4], &b[19 + i * 4]); \
		} \
	} while (0)

コンパイルして動けば OK です。

実行例
$ ./cpuminer -a lyra2rev2 -t 1 --benchmark
** cpuminer-multi 1.3.3 by tpruvot@github **
BTC donation address: 1FhDPLPpw18X4srecguG3MxJYe4a1JsZnd (tpruvot)

[2017-11-24 20:25:06] 1 miner threads started, using 'lyra2rev2' algorithm.
[2017-11-24 20:25:07] CPU #0: 68.54 kH/s
[2017-11-24 20:25:07] Total: 68.54 kH/s
[2017-11-24 20:25:11] Total: 80.77 kH/s
[2017-11-24 20:25:16] CPU #0: 80.76 kH/s
[2017-11-24 20:25:16] Total: 80.76 kH/s

もし高速化に挑むのであれば、ハッシュ関数の出力する結果が合っているかどうかも見た方が良いです。基本的には、変更前の結果と比べて同じかどうかをチェックします。まあ、マイニングプールに繋いでみて accept が返ることでも確かめられますけど、マイニングプールに迷惑なのでほどほどにね……。

コンパイラの本気を見よ

この素朴な実装はとても遅いです。我が家のマシン(AMD A10-7800/3.5GHz)では、最適化レベルが -O2 でも 33kH/s 程度しか出ません。cpuminer-multi の元々の実装(unrolling = 2)は 80〜81kH/s くらいなので、天と地ほどの差があります。

元の cpuminer-multi の実装が速い理由は、ラウンドの swap 処理を手動で解決し、2ラウンド分を unrolling しているからだと思われます。swap を手動展開するところで、記号の意味が訳わからなくなってしまうため、読むのはだいぶキツいものがあります。

ところがそこまでしなくても、実は -Ofast -march=native で最適化を掛けると 79〜80kH/s 程度と、かなり近い速度が出せてしまいます。出力されたコードは SSE によるベクタ化や命令並べ替えの多発で、人間には理解不能な感じになっちゃってますが、まーとにかく速いです。コンパイラの本気を見た気がしますね。

編集者: すずき(更新: 2017年 12月 7日 11:16)

コメント一覧

  • すずき 
    SHA-3 はもう決定していて、keccak が採用されたそうです。
    Wikipedia をちゃんと読んだら 「CubeHash は 2回戦までは行ったが、最終選考の 5つに残れなかった」と書いてありました。 
    (2017年11月26日 17:09:42)
open/close この記事にコメントする



2017年 11月 26日

仮想通貨とマイニング

マイニングは仮想通貨の決済システムを支える大事な計算のようです。仮想通貨のシステム維持に協力してくれてありがとう、という意味を込めてマイニングした人にはボーナスが与えられているんですね。

私も最初マイニングという単語から、仮想通貨が地面から沸いてくるようなイメージを持っていましたが、決してそんなことはなくて、マイニングには高いコスト、つまり、計算するハードの初期投資と維持する電気代が掛かっています。

日本は電気代が高くて、維持費と得られる仮想通貨の量(を日本円換算した額)が、割に合わないです。

もしモナコインを使ってみたいだけなら、日本円と仮想通貨を交換してくれるところから得るのが一番楽だと思います。仮想通貨を手に入れる手段として、マイニングはあまり効率が良いとは思いません。無駄に時間と金が掛かるだけです。もちろんマイニング自体に興味がある人は別ですよ。

先日私が調べていた CPU マイニング(2017年 11月 24日の日記参照)ではかなり「ハッシュ数/電力」の効率が悪く、マイニングの手法としては、ほぼ意味がありません。

現在 Lyra2REv2 は GPU マイニングが主流のようです。私も ccminer という CUDA を使ったマイナーを試しています。ローエンド GeForce GT 1030 1枚ですら 6MH/s で、CPU の 100倍くらいの速度です。すごいね、GPU って。

それでもマイニングするには貧弱な計算力なので、GPU 数枚程度ならマイニングプールを使うことになると思います。

メモ: 技術系の話は Facebook から転記しておくことにした。

編集者: すずき(更新: 2017年 11月 27日 01:17)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年 11月 30日

モナコインと CubeHash

先日(2017年 11月 24日の日記参照)CPU によるモナコインのマイニング cpuminer-multi について調べました。先日の成果としては、

  • CubeHash というハッシュ関数がとびきり時間が掛かっている
  • cpuminer-multi は既に手動で最適化されている
  • CubeHash を素朴に実装したら遅い
  • 素朴な実装でもコンパイラの最適化で cpuminer-multi の実装と同等の速度が出る

CubeHash を適当に SSE 化して遊んでいたところ、基本的には非常に遅く(改変前 80kH/s、改変後 30〜60kH/s)なりますが、突然 100kH/s に速くなるポイントがありました。なお、我が家のマシンは AMD A10-7800/3.5GHz です。

コンパイラの本気

急激に速くなった理由はおそらくコンパイラです。

途中までしか SSE 化していないはずなのに、逆アセンブラで見ると 1ラウンドが全てベクタ演算命令で記述されていること、また、コンパイラの最適化レベルを変えずに(Ofast)、ベクタ最適化だけ無効にすると、速度が 67kH/s に落ちることから、

  • 私が中途半端に SSE を使った
  • 変数間の依存性か何かが途切れた
  • コンパイラが残りの部分を全部ベクタ化できると判断
  • 1ラウンド全て SSE or AVX 化された

このようなメカニズムだろうと思っています。

平たく言えばコンパイラが本気出していなかっただけですね。1ラウンドを全てベクタ演算化すると、なんと 120kH/s も速度が出ました。

元のコードの 1.5倍の速度を拝めるとは思ってもいませんでした。何でもやってみるものですね!

Intel Intrinsics

SSE 化には Intel Intrinsics(マニュアル)を使いました、というより、Intrinsic が無かったら SSE 化をしようと思わないです。

Intrinsic はかなり強引ですけど、一応 C の関数として定義されており、人間が考えると面倒なこと(SSE レジスタ割り当て、退避など)は全てコンパイラがやってくれるため、大変便利です。

インラインアセンブラの一種とも言えますが、gcc のインラインアセンブラほど苦痛はありません。SSE/AVX を使いたいだけなら Intrinsic がおススメです。

手で頑張ってみよう

最初 CubeHash の STEP5(キューブの上面と下面の入れ替え操作)をシフトと OR で計算していたのですが、コンパイラが出す命令を見ていたら shuffle という素敵な命令を使っていたので、そっちで書き直してみました。

コンパイラ任せでも良いのですが、せっかく途中まで書いたので、全部 SSE 化しました。Before と After はこんな感じです。

SSE2 を使った CubeHash の素朴な実装

#define SSE_ROTL(x, n) do { \
		__m128i mw0, mw1; \
		mw0 = _mm_slli_epi32((x), (n)); \
		mw1 = _mm_srli_epi32((x), 32 - (n)); \
		x = _mm_or_si128(mw0, mw1); \
	} while (0);

#define SSE_SWP(a, b) do { \
		__m128i mw; \
		mw = b; \
		b = a; \
		a = mw; \
	} while (0);

#define ROUND_ONE    do { \
		__m128i mx0, mx4, mx8, mxc; \
		__m128i mxg, mxk, mxo, mxs; \
		mx0 = _mm_load_si128((void *)&x0); \
		mx4 = _mm_load_si128((void *)&x4); \
		mx8 = _mm_load_si128((void *)&x8); \
		mxc = _mm_load_si128((void *)&xc); \
		mxg = _mm_load_si128((void *)&xg); \
		mxk = _mm_load_si128((void *)&xk); \
		mxo = _mm_load_si128((void *)&xo); \
		mxs = _mm_load_si128((void *)&xs); \
		/* STEP1 */ \
		mxg = _mm_add_epi32(mx0, mxg); \
		mxk = _mm_add_epi32(mx4, mxk); \
		mxo = _mm_add_epi32(mx8, mxo); \
		mxs = _mm_add_epi32(mxc, mxs); \
		/* STEP2 */ \
		SSE_ROTL(mx0, 7); \
		SSE_ROTL(mx4, 7); \
		SSE_ROTL(mx8, 7); \
		SSE_ROTL(mxc, 7); \
		/* STEP3 */ \
		SSE_SWP(mx0, mx8); \
		SSE_SWP(mx4, mxc); \
		/* STEP4 */ \
		mx0 = _mm_xor_si128(mx0, mxg); \
		mx4 = _mm_xor_si128(mx4, mxk); \
		mx8 = _mm_xor_si128(mx8, mxo); \
		mxc = _mm_xor_si128(mxc, mxs); \
		/* STEP5 */ \
		mxg = _mm_shuffle_epi32(mxg, 0x4e); \
		mxk = _mm_shuffle_epi32(mxk, 0x4e); \
		mxo = _mm_shuffle_epi32(mxo, 0x4e); \
		mxs = _mm_shuffle_epi32(mxs, 0x4e); \
		/* STEP6 */ \
		mxg = _mm_add_epi32(mx0, mxg); \
		mxk = _mm_add_epi32(mx4, mxk); \
		mxo = _mm_add_epi32(mx8, mxo); \
		mxs = _mm_add_epi32(mxc, mxs); \
		/* STEP7 */ \
		SSE_ROTL(mx0, 11); \
		SSE_ROTL(mx4, 11); \
		SSE_ROTL(mx8, 11); \
		SSE_ROTL(mxc, 11); \
		/* STEP8 */ \
		SSE_SWP(mx0, mx4); \
		SSE_SWP(mx8, mxc); \
		/* STEP9 */ \
		mx0 = _mm_xor_si128(mx0, mxg); \
		mx4 = _mm_xor_si128(mx4, mxk); \
		mx8 = _mm_xor_si128(mx8, mxo); \
		mxc = _mm_xor_si128(mxc, mxs); \
		/* STEP10 */ \
		mxg = _mm_shuffle_epi32(mxg, 0xb1); \
		mxk = _mm_shuffle_epi32(mxk, 0xb1); \
		mxo = _mm_shuffle_epi32(mxo, 0xb1); \
		mxs = _mm_shuffle_epi32(mxs, 0xb1); \
		_mm_store_si128((void *)&x0, mx0); \
		_mm_store_si128((void *)&x4, mx4); \
		_mm_store_si128((void *)&x8, mx8); \
		_mm_store_si128((void *)&xc, mxc); \
		_mm_store_si128((void *)&xg, mxg); \
		_mm_store_si128((void *)&xk, mxk); \
		_mm_store_si128((void *)&xo, mxo); \
		_mm_store_si128((void *)&xs, mxs); \
	} while (0)

前回と同様に cpuminer-multi のマクロにはめ込めるように実装しています。

実行例
$ ./cpuminer -a lyra2rev2 -t 1 --benchmark
** cpuminer-multi 1.3.3 by tpruvot@github **
BTC donation address: 1FhDPLPpw18X4srecguG3MxJYe4a1JsZnd (tpruvot)

[2017-12-01 02:21:05] 1 miner threads started, using 'lyra2rev2' algorithm.
[2017-12-01 02:21:06] CPU #0: 140.04 kH/s
[2017-12-01 02:21:06] Total: 140.04 kH/s
[2017-12-01 02:21:10] Total: 145.47 kH/s
[2017-12-01 02:21:15] CPU #0: 145.32 kH/s
[2017-12-01 02:21:15] Total: 145.32 kH/s

CubeHash の最終 160ラウンドは一番のボトルネックだった個所だけあって、改善効果はかなり大きいですね。

編集者: すずき(更新: 2017年 12月 1日 02:24)

コメント一覧

  • AVXならこんな感じ? 
    /* STEP1 */ \
    mxg = _mm256_add_epi32(mx0, mxg); \
    mxo = _mm256_add_epi32(mx8, mxo); \
    /* STEP2 */ \
    AVX_ROTL(mx0, 7); \
    AVX_ROTL(mx8, 7); \
    /* STEP3 */ \
    AVX_SWP(mx0, mx8); \
    /* STEP4 */ \
    mx0 = _mm256_xor_si256(mx0, mxg); \
    mx8 = _mm256_xor_si256(mx8, mxo); \
    /* STEP5 */ \
    mxg = _mm256_permute4x64_epi64(mxg, 0xb1); \
    mxo = _mm256_permute4x64_epi64(mxo, 0xb1); \
    /* STEP6 */ \
    mxg = _mm256_add_epi32(mx0, mxg); \
    mxo = _mm256_add_epi32(mx8, mxo); \
    /* STEP7 */ \
    AVX_ROTL(mx0, 11); \
    AVX_ROTL(mx8, 11); \
    /* STEP8 */ \
    mx0 = _mm256_permute4x64_epi64(mx0, 0x4e); \
    mx8 = _mm256_permute4x64_epi64(mx8, 0x4e); \
    /* STEP9 */ \
    mx0 = _mm256_xor_si256(mx0, mxg); \
    mx8 = _mm256_xor_si256(mx8, mxo); \
    /* STEP10 */ \
    mxg = _mm256_shuffle_epi32(mxg, 0xb1); \
    mxo = _mm256_shuffle_epi32(mxo, 0xb1); \ 
    (2018年01月23日 09:38:41)
  • すずき 
    コメントありがとうございます。そのようになると思います。
    私の実装は下記のような感じです。STEP5, 8 が多少違うくらいですね。

    mxg = _mm256_add_epi32(mx0, mxg); \
    mxo = _mm256_add_epi32(mx8, mxo); \
    AVX_ROTL(mx0, 7); \
    AVX_ROTL(mx8, 7); \
    AVX_SWP(mx0, mx8); \
    mx0 = _mm256_xor_si256(mx0, mxg); \
    mx8 = _mm256_xor_si256(mx8, mxo); \
    mxg = _mm256_shuffle_epi32(mxg, 0x4e); \
    mxo = _mm256_shuffle_epi32(mxo, 0x4e); \
    mxg = _mm256_add_epi32(mx0, mxg); \
    mxo = _mm256_add_epi32(mx8, mxo); \
    AVX_ROTL(mx0, 11); \
    AVX_ROTL(mx8, 11); \
    mx0 = _mm256_permute2x128_si256(mx0, mx0, 0x01); \
    mx8 = _mm256_permute2x128_si256(mx8, mx8, 0x01); \
    mx0 = _mm256_xor_si256(mx0, mxg); \
    mx8 = _mm256_xor_si256(mx8, mxo); \
    mxg = _mm256_shuffle_epi32(mxg, 0xb1); \
    mxo = _mm256_shuffle_epi32(mxo, 0xb1);

    残念ながら AMD A10 は AVX2 に対応していないので、SSE2 との速度が比較できませんが…。 
    (2018年01月24日 14:40:47)
open/close この記事にコメントする



こんてんつ

open/close wiki
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報