macOSからPi Picoを使う - その31 [Pi Pico]
非同期転送のイベント処理に前回の専用threadを使うのが簡単そうなんだけど、そもそもlibusb_handle_events()の専用threadを起こすなら、そのthreadでずっと簡単な同期転送をすればいいような気がする。その話...
みたいな感じ。
終了処理をRunLoop経由でmain threadに渡すことができるなら、そのthreadで同期転送を起こして、転送が終わるとさっきと同じ機構でデータをmain threadに渡せばいい。
非同期転送は複数個の転送をsubmitできるが、同期転送の関数は転送が完了するまでブロックするので、転送をひとつずつシリアルに処理する必要がある。ただ、USBデバイスの方がホストよりずっと処理能力は低いのが普通なので、単一のデバイスに対して複数の転送submitが有用な場合というのは少ない気がする。
また例えば大量のbulk転送をしている途中にcontrol転送を割り込ませたいような場合、非同期転送ではそのタイミングをlibusbに任せることができる。しかしどのみち経路としては1つしかない(USBという全二重シリアル通信)ので、自分で間に割り込ませたとしても効率に違いはない。
従って多くの場合、専用threadを起こすなら非同期転送の代わりに同期転送を使っても大きな違いはなさそうである。ただしいくつか問題があって、まずこれだとisochronous転送が同期転送ではサポートされていないので、できない。また、libusb_***_tranfer()関数を呼んだあとに何らかの外部的な問題でキャンセルしたい、なんて場合もできない。
アナログで映像や音声を伝送する場合、送り終わってしまえばそのデータは消費されてしまうだけで、過去に遡ることもない。また、伝送途中でエラーが起こってもそれを修正する手段がない。エラーが起こればノイズになるが、その次の瞬間が正常ならそのエラーも忘れ去られるだけである。
デジタル伝送ではそんなことはないんだけど、アプリケーションとしてアナログ伝送と同じような場面がありえる。IEEE1394は汎用のデジタル伝送方式として考えられたけど、その規格が作られた1980年代後半には、音声はCDが普及してきてS/PDIFが機器間デジタル伝送として現れようとしていたころで、映像伝送はアナログが普通だった。当時のユースケースとしてはカムコーダの映像をテレビに映す、というような場合で、データそのものはカムコーダの磁気テープの中に閉じ込められて(保存されて)いる。IEEE1394はそういう時代の使い勝手として違和感がなかった。
ほぼ同時期にもっとヘビーデューティなアプリケーション用に考えられたFibre Channelにもisochronous転送モードがあるが、使われることはなかったんじゃないかという気がする。EthernetではTCPに対してUDPがエラーリカバリがないので、isochronous転送モードと見なすことがあるが、必ずしもUDPがそういう位置付けというわけではない。
しかしアナログ伝送の方がめずらしくなっている現代に、isochronous転送を使うのはリアルタイム性が優先されるaudio classやvideo classがほとんどで、音声や映像であってもデータとして保存する場合にはエラーリカバリが考慮された伝送モードの方が望ましい。つまりUSBではisochronous転送ではなくbulk転送になる。実際に「過去のデータも重要である」ようなマシンビジョン規格のUSB3 Visionでは当然isochronousではなくbulk転送が使われている。
USBではaudio classやvideo classの場合OSがサポートしているので、わざわざlibusbを使って独自実装する必要性は低い。専用のvendor specific classで何か積極的にisochronous転送を使う理由にどんな場合があるか、というと僕はすぐには思い浮かばない。
そう考えると同期転送でもよほどタイミングのクリティカルな場合以外は、適当なタイムアウトを設けて転送を失敗させてしまって、そのあと中途半端なデータを捨てれば、実質的に非同期転送でのキャンセル動作と大きな違いはないと思える。
実際、libusbを使った実装例として産業用カメラ接続ライブラリであるaravisは(usblib-0.1から使っているせいもあって)同期転送を専用のthreadで回すというやりかたで、大きな効率劣化なしにカメラからの大量のデータをさばいでいる。さっきも書いたようにUSB3 Visionではエラーリカバリなどをtransportレイヤに閉じ込めるためにbulk転送を使っているので、aravisがlibusbを使う上での影響はなかった。
ちなみにlibusbの同期転送そのものはこの非同期転送を使って実装されている。
17.6.2 別threadで非同期転送する意味があるか
専用threadで同期転送をするということは、つまりコードとしてはvoid *synchronous_thread_func(void *ctx) { while (event_thread_run) { pollRequest(); // wait for a trigger libusb_bulk_transfer(dev_handle, readEndPoint, data, length, &transfered, 1.0); dispatch_async_f(mainQueue, data, receiveFunction); } return NULL; }
終了処理をRunLoop経由でmain threadに渡すことができるなら、そのthreadで同期転送を起こして、転送が終わるとさっきと同じ機構でデータをmain threadに渡せばいい。
非同期転送は複数個の転送をsubmitできるが、同期転送の関数は転送が完了するまでブロックするので、転送をひとつずつシリアルに処理する必要がある。ただ、USBデバイスの方がホストよりずっと処理能力は低いのが普通なので、単一のデバイスに対して複数の転送submitが有用な場合というのは少ない気がする。
また例えば大量のbulk転送をしている途中にcontrol転送を割り込ませたいような場合、非同期転送ではそのタイミングをlibusbに任せることができる。しかしどのみち経路としては1つしかない(USBという全二重シリアル通信)ので、自分で間に割り込ませたとしても効率に違いはない。
従って多くの場合、専用threadを起こすなら非同期転送の代わりに同期転送を使っても大きな違いはなさそうである。ただしいくつか問題があって、まずこれだとisochronous転送が同期転送ではサポートされていないので、できない。また、libusb_***_tranfer()関数を呼んだあとに何らかの外部的な問題でキャンセルしたい、なんて場合もできない。
17.6.3 isochronous転送の実際
isochronous転送は「大量に、頻繁に転送が起こるけど遅れてはならない、そして最新のデータだけに意味がある」ような場合に使われる転送モードである。もともとはIEEE1394で映像音声を送るために考え出されたモードで、それをUSBが真似たものである。IEEE1394は古い規格で、デジタル伝送の利点をなるべく利用するように設計されているけど、転送帯域とリアルタイム性を優先する場合に限ってアナログ伝送のイメージを持ち込んだようなもので、つまり「過ぎ去ったことは気にしない」という思想である。アナログで映像や音声を伝送する場合、送り終わってしまえばそのデータは消費されてしまうだけで、過去に遡ることもない。また、伝送途中でエラーが起こってもそれを修正する手段がない。エラーが起こればノイズになるが、その次の瞬間が正常ならそのエラーも忘れ去られるだけである。
デジタル伝送ではそんなことはないんだけど、アプリケーションとしてアナログ伝送と同じような場面がありえる。IEEE1394は汎用のデジタル伝送方式として考えられたけど、その規格が作られた1980年代後半には、音声はCDが普及してきてS/PDIFが機器間デジタル伝送として現れようとしていたころで、映像伝送はアナログが普通だった。当時のユースケースとしてはカムコーダの映像をテレビに映す、というような場合で、データそのものはカムコーダの磁気テープの中に閉じ込められて(保存されて)いる。IEEE1394はそういう時代の使い勝手として違和感がなかった。
ほぼ同時期にもっとヘビーデューティなアプリケーション用に考えられたFibre Channelにもisochronous転送モードがあるが、使われることはなかったんじゃないかという気がする。EthernetではTCPに対してUDPがエラーリカバリがないので、isochronous転送モードと見なすことがあるが、必ずしもUDPがそういう位置付けというわけではない。
しかしアナログ伝送の方がめずらしくなっている現代に、isochronous転送を使うのはリアルタイム性が優先されるaudio classやvideo classがほとんどで、音声や映像であってもデータとして保存する場合にはエラーリカバリが考慮された伝送モードの方が望ましい。つまりUSBではisochronous転送ではなくbulk転送になる。実際に「過去のデータも重要である」ようなマシンビジョン規格のUSB3 Visionでは当然isochronousではなくbulk転送が使われている。
USBではaudio classやvideo classの場合OSがサポートしているので、わざわざlibusbを使って独自実装する必要性は低い。専用のvendor specific classで何か積極的にisochronous転送を使う理由にどんな場合があるか、というと僕はすぐには思い浮かばない。
17.6.4 転送のキャンセル
転送キャンセルに関してもlibusbの同期転送と非同期転送で大きく違う場面というのはそれほど多くない。すでに転送が開始されている場合は非同期転送でキャンセルできたとしてもUSBの転送フレームの途中でキャンセルできるわけではなく、整合性を確保するための後始末は面倒な場合が多い。実質的に転送が始まる前にキャンセルできなければ、最後まで転送してそのあと転送を無かったことにする(データを捨てる)ことにしたほうが簡単である。そう考えると同期転送でもよほどタイミングのクリティカルな場合以外は、適当なタイムアウトを設けて転送を失敗させてしまって、そのあと中途半端なデータを捨てれば、実質的に非同期転送でのキャンセル動作と大きな違いはないと思える。
実際、libusbを使った実装例として産業用カメラ接続ライブラリであるaravisは(usblib-0.1から使っているせいもあって)同期転送を専用のthreadで回すというやりかたで、大きな効率劣化なしにカメラからの大量のデータをさばいでいる。さっきも書いたようにUSB3 Visionではエラーリカバリなどをtransportレイヤに閉じ込めるためにbulk転送を使っているので、aravisがlibusbを使う上での影響はなかった。
ちなみにlibusbの同期転送そのものはこの非同期転送を使って実装されている。
2021-09-19 21:04
nice!(0)
コメント(0)
コメント 0