肉メモ
西友の980円肩ロース(厚手)。
QOL向上のため、この手の肉の目利き能力を上げる。
優秀部位の多い肉を選べるように研究するのだ。
肩ロースステーキを今まで散々バカにしてきたわけだが安さは偉大。
明らかにスジ肉の部位は取り除いてカレー用にする。
厚さ3ミリの鉄のフライパンで表裏メイラード反応が出るまで焼き上げてから濡れ布巾の上にパンを置いてアルミホイルで蓋をし蒸らすこと三分。
香りづけのガーリックも付着したまま一緒に焼く。
ガーリックは焼き上がりに取り除いて食さない。
(廃棄の肉汁とガーリックはカレーに入れた)
皿に盛る都合で①③④⑤は鉛直方向を軸に180度回転状態。
焼き加減はたんぱく質凝固による柔らかさの違いが出やすいミディアムレア。
味付けは塩胡椒ガーリックのみ。
ソースなし。
①
やわらか、ジューシー。
もっともステーキに向いている。
この部位は最後に投入したので、柔らかさは焼き加減によるところがあるかもしれない。
だが過去の経験からもこの部位が最もステーキに向いているのは薄々感づいていた。
どうやらここがロース芯と呼ばれる部位のようだ。
肩ロースからはこの程度の割合でしか取れない。とはいえ、今回の肉は比較的リブロース側の肩ロースと思われ、これでも多い方だと思う。
②
やわらか、ややスジっぽい。
スジが口内に残る。
カレー行きにすべき。
①の部位とちゃんと分離するのが重要なことがわかる。
③
やや硬い。スジっぽい。
太いスジが横方向に走っているので長いスジに当たりやすい。
④
やわらか。ジューシーさは①に劣る。
右に行くほど脂っこく、柔らかい。
しかし、スジも増える。
(まじで増える。飲み込めないレベル。)
⑤
コリコリとした歯ごたえ。
最も肉の味が濃い。
スジは左側に行くほどある。
左端は飲み込むのが辛いレベルでスジが残る。
縦にスジがあるのでナイフで切って短くできない。
食べきれず吐き出すスジ多し。
RICOH THETAで撮った写真をPTP-IPで取得する
前回シャッター操作をしたので、撮った写真の取得もしたくなることでしょう。
今回もRuby-PTP-IPをベースにして写真の取得シーケンスとコードを解説します。
THETAでの画像取得について
PTP対応機種では写真を撮ると、画像、サムネイル、メタデータが作成されます。
それらはまとめて一つの32bit整数が割り当てられます。
取得、変更、削除はその値を指定してアクセスすることになります。
これをオブジェクトハンドルといいます。一意の値です。
同一のオブジェクトハンドルに対して
- 画像を取得する時はGetObject
- サムネイルはGetThumb
- メタデータはGetObjectInfo
と、オペレーションコードを変えてアクセスすることでそれぞれのデータを得る形です。
セッション内で取得したオブジェクトハンドルはそのセッション中でのみ一意性が保証されます。
まあ通常、セッションまたいでも同一ですが。
オブジェクトハンドルの取得についてですが、
前回のシャッター操作のようにInitiateCapture後はObjectAddedイベントが来ます。
そして、そのイベントの一つ目のパラメータに新しいオブジェクトハンドルが格納されています。
・・・が、THETAでは常に100が入っていて、そのオブジェクトハンドルでアクセスしてもエラーとして
レスポンスコード:PTP_RC_InvalidObjectHandle
が帰ってきます。(2014/08/24 : こちらのバグでした)
よって、今回のサンプルではTHETA内のオブジェクトハンドルを全て取得しています。
そしてTHETAではその末尾が最も新しいオブジェクトハンドルなのでその値を使用しています。
シーケンス
シーケンス図では長くなるので画像とサムネイルにおけるデータフェイズの記述を省略しています。
今回はイベントコネクションを使ったセッション中の通信は無いです。
THETAではイベントコネクションを開いていなくてもオペレーションリクエストを受付るらしいので、画像取得だけなら省略してしまってもいいでしょう。
コード
接続やセッションの部分は前回(修正版)と同じです。
今回はセッション中にサムネイルと画像を受信して保存しているだけです。
簡単ですね。
#!/bin/ruby require 'socket' require './ruby_ptp_ip/ptp.rb' require './ruby_ptp_ip/ptp_ip.rb' # THETAのIPアドレスとPTP-IPのポート番号 ADDR = "192.168.1.1" PORT = 15740 # THETAに送るレスポンダ情報 NAME = "Ruby_THETA_GetObject" GUID = "ab653fb8-4add-44f0-980e-939b5f6ea266" PROTOCOL_VERSION = 65536 # GUID文字列を整数の配列にする関数 def str2guid(str) hexes = str.scan /([a-fA-F0-9]{2})-*/ hexes.flatten! raise "Invalid GUID" if hexes.length != 16 hexes.map do |s| s.hex end end # パケットを書き込む関数 def write_packet(sock, pkt) sock.send(pkt.to_data.pack("C*"), 0) end # パケットを読み込む関数 def read_packet(sock) data = [] data += sock.read(PTPIP_packet::MIN_PACKET_SIZE).unpack("C*") len = PTPIP_packet.parse_length(data) data += sock.read(len-PTPIP_packet::MIN_PACKET_SIZE).unpack("C*") PTPIP_packet.new(data) end # データフェイズの読み込み関数 def recv_data(sock, transaction_id) recv_pkt = read_packet(sock) raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_StartDataPacket raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id data_len = recv_pkt.payload.total_data_length_low data = [] while recv_pkt.type != PTPIP_PT_EndDataPacket recv_pkt = read_packet(sock) raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_DataPacket && recv_pkt.type != PTPIP_PT_EndDataPacket raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id data += recv_pkt.payload.data_payload end raise "Invalid Data Size" unless data_len == data.length return data end # コマンドコネクション初期化関数 def init_command(sock) init_command_payload = PTPIP_payload_INIT_CMD_PKT.new() init_command_payload.guid = str2guid(GUID) init_command_payload.friendly_name = NAME init_command_payload.protocol_version = PROTOCOL_VERSION init_command = PTPIP_packet.create(init_command_payload) write_packet(sock, init_command) read_packet(sock)#ACK end # イベントコネクション初期化関数 def init_event(sock, conn_number) init_event_payload = PTPIP_payload_INIT_EVENT_REQ_PKT.new() init_event_payload.conn_number = @conn_number init_event = PTPIP_packet.create(init_event_payload); write_packet(sock, init_event) read_packet(sock) end # データフェイズのあるオペレーションコード実行関数 def data_operation(sock, operation_code, transaction_id, parameters = []) op_payload = PTPIP_payload_OPERATION_REQ_PKT.new() op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE op_payload.operation_code = operation_code op_payload.transaction_id = transaction_id op_payload.parameters = parameters op_pkt = PTPIP_packet.create(op_payload) write_packet(sock, op_pkt) data = recv_data(sock, transaction_id) recv_pkt = read_packet(sock) return recv_pkt, data end # シンプルなオペレーションコードの実行関数 def simple_operation(sock, operation_code, transaction_id, parameters = []) op_payload = PTPIP_payload_OPERATION_REQ_PKT.new() op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE op_payload.operation_code = operation_code op_payload.transaction_id = transaction_id op_payload.parameters = parameters op_pkt = PTPIP_packet.create(op_payload) write_packet(sock, op_pkt) read_packet(sock) end # # ここから処理 # TCPSocket.open(ADDR, PORT) do |s| # コマンドコネクション recv_pkt = init_command(s) raise "Initialization Failed (Command) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket p @conn_number = recv_pkt.payload.conn_number p @guid = recv_pkt.payload.guid p @name = recv_pkt.payload.friendly_name p @protocol_version = recv_pkt.payload.protocol_version TCPSocket.open(ADDR, PORT) do |es| # イベントコネクション recv_pkt = init_event(es, @conn_number) raise "Initialization Failed (Event) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket print "Command/Event Connections are established.\n" @transaction_id = 1 @session_id = 1 # セッション開始 recv_pkt = simple_operation(s, PTP_OC_OpenSession, @transaction_id, [@session_id]) raise "Open Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK @transaction_id += 1 # オブジェクトの列挙 # 1つ目のパラメータはストレージID. 0xFFFFFFFFのときは全てのストレージから列挙 # 2つめのパラメータはフォーマットID. 0xFFFFFFFFの時は全てのフォーマット. # 3つめのパラメータはオプション.場合によって異なるが、今回の全列挙の場合は0. recv_pkt, data = data_operation(s, PTP_OC_GetObjectHandles, @transaction_id, [0xFFFFFFFF, 0xFFFFFFFF, 0]) raise "GetObjectHandles Failed #{recv_pkt.payload.reason_code}" if recv_pkt.payload.response_code != PTP_RC_OK offset = 0 @object_handles, offset = PTP_parse_long_array(offset, data) p @object_handles #オブジェクトハンドルのダンプ @transaction_id += 1 print "GetThumb...\n" # 最後に撮ったサムネイルの取得 # 一番目のパラメータにObjectHandleを格納 recv_pkt, data = data_operation(s, PTP_OC_GetThumb, @transaction_id, [@object_handles[-1]]) raise "GetThumb Failed #{recv_pkt.payload.reason_code}" if recv_pkt.payload.response_code != PTP_RC_OK File.open("./theta_thumb.jpg", "wb") do |f| f.write(data.pack("C*")) print "Saved!\n" end @transaction_id += 1 print "GetObject...\n" # 最後に撮った写真の取得 # 一番目のパラメータにObjectHandleを格納 recv_pkt, data = data_operation(s, PTP_OC_GetObject, @transaction_id, [@object_handles[-1]]) raise "GetObject Failed #{recv_pkt.payload.reason_code}" if recv_pkt.payload.response_code != PTP_RC_OK File.open("./theta_pic.jpg", "wb") do |f| f.write(data.pack("C*")) print "Saved!\n" end @transaction_id += 1 # セッション終了 recv_pkt = simple_operation(s, PTP_OC_CloseSession, @transaction_id) raise "Close Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK @transaction_id += 1 end end
プログラム的にサムネイル、画像を取得できるようになれば定期的な撮影とアップロードとかを自動で出来ますね。
RICOHのページにアップする以外にもウェブページに埋め込んだり、アプリに埋め込んだりという選択肢もあるので、以下のプログラムと組み合わせるといいかもです。
ブラウザで見る場合は
Haga氏のビューワ(github)
https://github.com/thaga/IOTA
akokubo氏のビューワ(github)
https://github.com/akokubo/ThetaViewer
Oculus持っている人は
MobileHackerzの中の人のOculus用ビューワ
http://mobilehackerz.jp/contents/Review/RICOH_THETA/Oculus
とかあります。
RICOH THETAでRuby-PTP-IPの紹介
RICOH THETAってPTP-IPで制御できるんですね↓
http://mobilehackerz.jp/contents/Review/RICOH_THETA/WiFi_Control
昔作ったPTP-IPのライブラリがあるのでRubyからシャッターを動作させてみます。
今後も時間を作って、写真の取得などの記事も書きたいですね。 →書いた
ライブラリは、この記事を書くまではruby_ptp_responderと言うプロジェクト名でしたが少しキャッチーにruby_ptp_ipへと変更しました。
こちら(github)
https://github.com/stoikheia/ruby_ptp_ip
に置いてあります。
今見るとmoduleでまとめていなかったりするのがムズムズしますね。
Rubyでどうやって動かすのか、という点に興味ある方は最小コードでのシャッター制御をGOROman氏が公開しているので↓
https://gist.github.com/GOROman/7596186#file-thetatest-rb
そちらのほうがまずいいかもです。
まあー、自分がやることは既に無い感じですけど、
ライブラリの紹介ついでの記事にします。
(2014/08/23修正)
シーケンス
THETAのシャッターを動作させる手順は、以下のシーケンスです。
(青いノートはRuby-PTP-IP上での説明です)
より詳細に知りたい人は
PTP(PIMA15740-2000)とPTP-IP(CIPA_DC-005-2005)を読むと良いです。
THETA側がレスポンダと呼ばれる、機能を提供する側です。
要求を出すこっち側はイニシエータと呼ばれます。
コマンド用とイベント用それぞれ2つのコネクションを確立します。
上の図では緑の矢印がコマンドコネクション、
赤の矢印がイベントコネクションでの送受信として見てください。
イベント用のコネクションを開いた後、
InitCommandRequestのAckで取得したコネクションIDをInitEventRequestで渡し、
コマンドとイベントそれぞれのコネクションの対応をTHETAに教えます。
GetDeviceInfoはリクエストを送った後にデータフェイズになるオペレーションコードです。
DeviceInfoがデータパケットで送られてきた後、データフェイズが完了してからレスポンスパケットが帰ってきます。
DeviceInfoの取得は必須ではないです。
OpenSessionでセッションを開始後、シャッターを動作させるのですが
コマンドには同期コマンドと非同期コマンドがあり、
THETAのシャッターを開始するInitiateCaptureは非同期コマンドです。
THETAがコマンドを受け取った後に、
イベント用のコネクションからObjectAddedイベント、続いてCaptureCompleteイベントが来ます。
シャッター操作はコネクション確立、データフェイズ、セッション、非同期イベントと、PTP-IPの要素が全て入っているのでPTP-IPの学習にもってこいですね。
コード
このライブラリはレスポンダという、いわばTHETA側の機能を実現するために作ったものですが、PTP-IPの命令とパケットタイプはすべて実装してあるのでTHETAに命令するイニシエータとしても使えます。
汎用性のためにプリミティブなライブラリになっているので、PTP-IPのパケットやデータをSocketなどで送受信する処理は使用者が書くようになっていますが、そこはRubyなら簡単に書ける部分でしょう。
ライブラリの使い方全般として、
送信したい場面に応じて
payload = PTPIP_payload_HOGE.new() payload.aaaa = bbbb #データ格納とか packet = PTPIP_packet.create(payload)
パケットクラスはto_dataメソッドでバイト配列を作成するので
それをpack("C*")でStringにしてSocketに書き込みます。
write_socket(packet.to_data.pack("C*")) #to_dataの時点ではバイト配列なのでpackする
逆に受信したデータは
- unpack("C*")バイト配列にする
- PTPIP_packet.new()に渡す
packed_data = read_socket() packet = PTPIP_packet.new(packed_data.unpack("C*")) p packet.payload.cccc #データに応じたペイロードが格納されている
という流れです。
GUIDは適当につくりました。
縦に長いコードですが、
- 値を入れる
- 送る
- 受信
の繰り返しなだけなので内容は簡単だと思います。
#!/bin/ruby require 'socket' require './ruby_ptp_ip/ptp.rb' require './ruby_ptp_ip/ptp_ip.rb' # THETAのIPアドレスとPTP-IPのポート番号 ADDR = "192.168.1.1" PORT = 15740 # THETAに送るレスポンダ情報 NAME = "Ruby_THETA_Shutter" GUID = "ab653fb8-4add-44f0-980e-939b5f6ea266" PROTOCOL_VERSION = 65536 # GUID文字列を整数の配列にする関数 def str2guid(str) hexes = str.scan /([a-fA-F0-9]{2})-*/ hexes.flatten! raise "Invalid GUID" if hexes.length != 16 hexes.map do |s| s.hex end end # パケットを書き込む関数 def write_packet(sock, pkt) sock.send(pkt.to_data.pack("C*"), 0) end # パケットを読み込む関数 def read_packet(sock) data = [] data += sock.read(PTPIP_packet::MIN_PACKET_SIZE).unpack("C*") len = PTPIP_packet.parse_length(data) data += sock.read(len-PTPIP_packet::MIN_PACKET_SIZE).unpack("C*") PTPIP_packet.new(data) end # データフェイズの読み込み関数 def recv_data(sock, transaction_id) recv_pkt = read_packet(sock) raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_StartDataPacket raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id data_len = recv_pkt.payload.total_data_length_low data = [] while recv_pkt.type != PTPIP_PT_EndDataPacket recv_pkt = read_packet(sock) raise "Invalid Packet : #{recv_pkt.to_s}" if recv_pkt.type != PTPIP_PT_DataPacket && recv_pkt.type != PTPIP_PT_EndDataPacket raise "Invalid Transaction ID" if recv_pkt.payload.transaction_id != transaction_id data += recv_pkt.payload.data_payload end raise "Invalid Data Size" unless data_len == data.length return data end # コマンドコネクション初期化関数 def init_command(sock) init_command_payload = PTPIP_payload_INIT_CMD_PKT.new() init_command_payload.guid = str2guid(GUID) init_command_payload.friendly_name = NAME init_command_payload.protocol_version = PROTOCOL_VERSION init_command = PTPIP_packet.create(init_command_payload) write_packet(sock, init_command) read_packet(sock)#ACK end # イベントコネクション初期化関数 def init_event(sock, conn_number) init_event_payload = PTPIP_payload_INIT_EVENT_REQ_PKT.new() init_event_payload.conn_number = @conn_number init_event = PTPIP_packet.create(init_event_payload); write_packet(sock, init_event) read_packet(sock) end # データフェイズのあるオペレーションコード実行関数 def data_operation(sock, operation_code, transaction_id, parameters = []) op_payload = PTPIP_payload_OPERATION_REQ_PKT.new() op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE op_payload.operation_code = operation_code op_payload.transaction_id = transaction_id op_payload.parameters = parameters op_pkt = PTPIP_packet.create(op_payload) write_packet(sock, op_pkt) data = recv_data(sock, transaction_id) recv_pkt = read_packet(sock) return recv_pkt, data end # シンプルなオペレーションコードの実行関数 def simple_operation(sock, operation_code, transaction_id, parameters = []) op_payload = PTPIP_payload_OPERATION_REQ_PKT.new() op_payload.data_phase_info = PTPIP_payload_OPERATION_REQ_PKT::NO_DATA_OR_DATA_IN_PHASE op_payload.operation_code = operation_code op_payload.transaction_id = transaction_id op_payload.parameters = parameters op_pkt = PTPIP_packet.create(op_payload) write_packet(sock, op_pkt) read_packet(sock) end # # ここから処理 # TCPSocket.open(ADDR, PORT) do |s| # コマンドコネクション recv_pkt = init_command(s) raise "Initialization Failed (Command) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket p @conn_number = recv_pkt.payload.conn_number p @guid = recv_pkt.payload.guid p @name = recv_pkt.payload.friendly_name p @protocol_version = recv_pkt.payload.protocol_version TCPSocket.open(ADDR, PORT) do |es| # イベントコネクション recv_pkt = init_event(es, @conn_number) raise "Initialization Failed (Event) #{recv_pkt.payload.reason}" if recv_pkt.type == PTPIP_PT_InitFailPacket print "Command/Event Connections are established.\n" @session_id = 1 @transaction_id = 1 # DeviceInfoの取得とパースと表示 recv_pkt, data = data_operation(s, PTP_OC_GetDeviceInfo, @transaction_id) dev_info = PTP_DeviceInfo.create(data) print "Device Info :\n" p dev_info.to_s raise "GetDeviceInfo Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK @transaction_id += 1 # セッション開始 recv_pkt = simple_operation(s, PTP_OC_OpenSession, @transaction_id, [@session_id]) raise "Open Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK @transaction_id += 1 # キャプチャ開始 # [0,0]はストレージIDとキャプチャフォーマット # それぞれ0の時はデバイス側が判断する. recv_pkt = simple_operation(s, PTP_OC_InitiateCapture, @transaction_id, [0,0]) raise "Initiate Capture Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK @transaction_id +=1 @event_transaction_id = recv_pkt.payload.transaction_id print "Capturing...\r" # ObjectAddedイベントの待機 recv_pkt = read_packet(es) raise "Invalid Event Packet" unless recv_pkt.type == PTPIP_PT_EventPacket raise "Invalid Event Code" unless recv_pkt.payload.event_code == PTP_EC_ObjectAdded print "Object Added!\n" # CaptureCompletedイベントの待機 recv_pkt = read_packet(es) raise "Invalid Event Packet" unless recv_pkt.type == PTPIP_PT_EventPacket raise "Invalid Event Code" unless recv_pkt.payload.event_code == PTP_EC_CaptureComplete print "Capture Completed!\n" # セッション終了 recv_pkt = simple_operation(s, PTP_OC_CloseSession, @transaction_id) raise "Close Session Failed #{recv_pkt.payload.response_code}" if recv_pkt.payload.response_code != PTP_RC_OK @transaction_id += 1 end end
イベントのトランザクションIDはめちゃくちゃな値が入っているのでエラーチェックに使えなかったです。(2014/08/23 : こちらのバグでした。)
THETAのPTP-IPでのデバイス情報
上のコードでもDeviceInfoをダンプしているので確認できると思いますが表にしておきます。
ベンダ独自のコードもあるので、そのあたりまだ発掘しがいがありそうです。
使えるオペレーションコード
オペレーションコード | 値 |
---|---|
PTP_OC_GetDeviceInfo | 0x1001 |
PTP_OC_OpenSession | 0x1002 |
PTP_OC_CloseSession | 0x1003 |
PTP_OC_GetStorageIDs | 0x1004 |
PTP_OC_GetStorageInfo | 0x1005 |
PTP_OC_GetNumObjects | 0x1006 |
PTP_OC_GetObjectHandles | 0x1007 |
PTP_OC_GetObjectInfo | 0x1008 |
PTP_OC_GetObject | 0x1009 |
PTP_OC_GetThumb | 0x100A |
PTP_OC_DeleteObject | 0x100B |
PTP_OC_InitiateCapture | 0x100E |
PTP_OC_GetDevicePropDesc | 0x1014 |
PTP_OC_GetDevicePropValue | 0x1015 |
PTP_OC_SetDevicePropValue | 0x1016 |
???? | 0x1022 |
使用されるイベントコード
イベントコード | 値 |
---|---|
PTP_EC_ObjectAdded | 0x4002 |
PTP_EC_DevicePropChanged | 0x4006 |
PTP_EC_StoreFull | 0x400a |
PTP_EC_CaptureComplete | 0x400d |
使用できるデバイスプロパティコード
デバイスプロパティコード | 値 |
---|---|
PTP_DPC_BatteryLevel | 0x5001 |
PTP_DPC_ExposureBiasCompensation | 0x5010 |
PTP_DPC_DateTime | 0x5011 |
PTP_DPC_CaptureDelay | 0x5012 |
???? | 0x502C |
???? | 0xD006 |
???? | 0xD801 |
???? | 0xD802 |
???? | 0xD803 |
???? | 0xD805 |
???? | 0xD806 |
???? | 0xD807 |
可能なキャプチャフォーマット
キャプチャフォーマット | 値 |
---|---|
PTP_OFC_Association | 0x3001 |
PTP_OFC_EXIF_JPEG | 0x3801 |
(と出ているものの、InitiateCaptureで0(デバイスまかせ)以外を指定するとParameterNotSupportedになりました)
可能なイメージフォーマット
イメージフォーマット | 値 |
---|---|
PTP_OFC_Association | 0x3001 |
PTP_OFC_EXIF_JPEG | 0x3801 |
sinf()の結果がVCとclang/gccで違う件について
VCとclang/gccで単精度浮動小数点数での三角関数の結果が微妙に違う。
まず以下のコード。
main.c
#define my_sinf(x) (float)sin((double)(x)) int main() { int tmp; float val; float ret_sin, ret_sinf, ret_my_sinf; tmp = 0x3e75565e; memcpy(&val, &tmp, 4); ret_sin = sin((double)val);//倍精度用 ret_sinf = sinf(val);//単精度 ret_my_sinf = my_sinf(val);//自作単精度 printf("ret_sin : 0x%08x\n", *((int*)&ret_sin)); printf("ret_sinf : 0x%08x\n", *((int*)&ret_sinf)); printf("ret_my_sinf : 0x%08x\n", *((int*)&ret_my_sinf)); return 0; }
VCの結果
> cl.exe main.c
ret_sin : 0x3e72ff38 ret_sinf : 0x3e72ff38 <--- 最下位桁が8 ret_my_sinf : 0x3e72ff38
clang/gccの結果
$ clang --std=c99 -o test main.c
ret_sin : 0x3e72ff38 ret_sinf : 0x3e72ff39 <--- 最下位桁が9 ret_my_sinf : 0x3e72ff38
この結果の違いから、
VCにおいては sinf() は my_sinf() と同様に
内部で倍精度浮動小数点数で計算したあとで単精度にして返しているのに対し、
clang/gccでは、内部も単精度浮動小数点数で計算しているであろうというのがわかる。
(手元にある別のlibcのソースを見たら内部が単精度だったし(適当))
C言語の仕様としては計算の精度までは指定していないためこれは仕方が無いのですが、
同じ計算なのにビルド環境によって結果が違うというのは
まじめな計算をしている時には厄介なバグになるので注意ですね。実際死んだ。
こういうとき、いつもなら気晴らしにVCを叩くところだけど
さすがに精度がいいほうを叩くのはなんか気が引けるっすね
GASで記述したWebアプリ上で画面遷移
仕事じゃなく、さらに維持費とかが考えに無い知人からの
「こういうの欲しい。」
といった相談は、作ったら相手に投げっぱなしでおkな
Google SitesとGoogle App Script (GAS)で対応する事が多い。
一応、この2つについて少し紹介すると
GoogleSitesはWYSIWYGでWebページを作れるサービスで、
GASはGoogleDriveのサーバー上で動くjavascriptのこと。
GASはGoogleDrive上でスプレッドシートなんかを処理するのに利用される事もあるけど、今回の話しはGASを使ってGoogleDrive上で動くWebアプリケーションを記述する機能についてのお話。
で、今回はちょっと複雑な入力を処理する事になったので、
GAS内で画面遷移を作る必要があった。
この場合の画面遷移とは、
同一URL内で、(ページ遷移をせずに)UIを切り替える事。
例えば、最初の画面から"次へ"ボタンを押すと
次となる画面が表示されるような形のことだ。
まず、GASにはUiInstanceというUI(ボタンやラベル、テキストボックス)
を保持するルートとなるオブジェクトがあり、全てのUIは
このオブジェクトから生成され、UiInstance自身にaddされることで
表示される画面を構成できる。
こんな感じ
//リクエスト function doGet() { var app = UiApp.createApplication(); //UiInstance作成 app.add(app.createButton("hoge")); //ボタン追加 return app; //hogeボタンのある画面が表示される }
画面をルートから入れ替えるにはUiInstance単位で操作すればいいのでは?
つまり新しくUiInstanceを作成してセットすればいいというのが筋というか、
そういう感じだろう!
と思ってリファレンスを調べていたけど全く手応えが無い。
そしてこの方法ではエラーが発生。
function eventHandler(e) { var app = UiApp.getActiveApplication();//既存の画面を取得 /* ... */ var new_app = UiApp.createApplication();//新しい画面を作成 /* ... */ // ヤバげなerror! }
現状のGASでは複数のUiInstanceを作成する事が想定されていない様子。
ということで、
画面遷移があるにもかかわらず一つのUiInstanceを使い回すことになると、
画面を移動するたびにUIを全て削除して追加して...
といった冗長なコードを書かないとならなくなる。
海外のフォーラムでもUiInstanceの切り替えができないことについて
相談があったりしたのでそれを読んだ感じ、
画面全体を占めるPanelを作って
それにUIを載せていけばPanel単位で管理出来るよというアイデアがあった。
と言う事で以下に画面遷移のサンプル。
- 一つの画面に対してPanel自体を生成、破棄して実現
- 画面の切り替えは一つのPanelを継続して使用する。clear()して新しい画面のUIをそのPanelに改めて構築することで実現
という実装の2パターン。
サンプルコード(GoogleDrive)
サンプルコード(Gist)
ちなみにUiInstance自身にはclear()が無いのがキモで、
UIをひとまとめに管理するには
なにかしらのコンテナとなるUIが必要ということになっています。
Gridでもいいんだけど今回はそれがPanelだということでした。
無い!無い!(git)
共有リポジトリからgit cloneしてきたら作業ファイルが無い。
.git以下はちゃんと中身が詰まっているのに何故?
というのが今日やられたところ。
.gitはあるからrevertしてみようとか、pullするといいんじゃね?とかやってみたけどgitさんはエラーを返すばかり。
ギブアップして共有リポジトリに問題が無いかlogを見ようとしたら、原因へのとっかかりを得る事が出来た。
$ cd shared_repo.git $ git log fatal: bad default revision 'HEAD' $ cat HEAD ref: refs/heads/master
多分、共有リポジトリの作り方が悪かったんだ。
(と、そのときは考えた)
この共有リポジトリは大元の作業ローカルから
$ git clone --bare /hoge/huga shared_repo.git
してきたもの。
最初に空の共有リポジトリを作って、ローカルにcloneして変更を加えていったものではなかった。
そして、元作業ローカルに移動してHEADを確認ついでにbranchを確認したときに根本の問題が判明した。
$ git branch * (no branch)
oh... masterブランチが無い(何故?)
今まで延々とno branchにコミットして今に至るらしい。
no branchの状態を是正すればきっとうまくいく。
$ git branch master $ git branch * (no branch) master $ git checkout master $ git branch * master
と、(当然ながら)masterを作って切り替えるだけでいままでのlogも全てmasterに移行できた。
どういう経緯でno branchのみとなったのかは不明でした。
このあと、共有リポジトリを作り直し、そこから再び最初の目的であったcloneをしたら作業ファイルがちゃんと展開されていました。
おしまいっと。
昨日twitterで日中に流れていた話題。
インクリメントは前置きの方がパフォーマンスが良くなるというアレをjavascriptの世界で議論していた。
話題としては、「前置きの方が早くなるよね」「でもJITで最適化が効きそうだよね」としつつ、前置きが無難。という結論だったかんじ。
夜になるとvolatileの話題のほうが盛り上がって忘れ去られていって残念だったけど
JavaScriptでインクリメントは変数の前、後ろどちらの方が高速?
こちらでベンチマークが紹介されていました。
実測すると・・・
となり、意外や意外。
Chromeでは後置きの方が圧倒的に早いという結果になった!
(*原因は後述)
前置きが他のブラウザより酷く遅いというわけではなく、
後置きがめちゃくちゃ早い!
ここまで差が出るのは
javascriptエンジンがどんな最適化をしている影響か気になるよね。
v8の生成する実行コードを見たくなった。
それにあたってまずエンジン単体での
スクリプトの実行時間(とコンパイル時間)を計る。
ここを参考にlibv8を導入して、
Hello,World!を少しいじってclock()関数で計測。
#include <iostream> #include <v8.h> static const std::string preparation_code = "var data = new Array(500 * 1000);var index = data.length - 1;while (index--) data[index] = index;"; static const std::string prefix_increment = "for (var index = 0, len = data.length; index < len; ++index) {data[index] = data[index] * 2;}"; static const std::string suffix_increment = "for (var index = 0, len = data.length; index < len; index++) {data[index] = data[index] * 2;}"; void run_preparation(void); void run_prefix(void); void run_suffix(void); void run_script(const std::string &test_name, const std::string &js); int main (int argc, const char * argv[]) { run_prefix(); run_suffix(); return 0; } void run_preparation() { v8::HandleScope handle_scope; //preparation v8::Handle<v8::String> source = v8::String::New(preparation_code.c_str()); v8::Handle<v8::Script> script = v8::Script::Compile(source); v8::Handle<v8::Value> result = script->Run(); } void run_prefix() { run_script("prefix-test", prefix_increment); } void run_suffix() { run_script("suffix-test", suffix_increment); } void run_script(const std::string &test_name, const std::string &js) { clock_t start, end; v8::HandleScope handle_scope; v8::Persistent<v8::Context> context = v8::Context::New(); context = v8::Context::New(); v8::Context::Scope context_scope(context); //preparation run_preparation(); //increment v8::Handle<v8::String> source = v8::String::New(js.c_str()); start = clock(); v8::Handle<v8::Script> script = v8::Script::Compile(source); end = clock(); std::cout << test_name << " : compile = " << end - start << std::endl; start = clock(); v8::Handle<v8::Value> result = script->Run(); end = clock(); std::cout << test_name << " : execute = " << end - start << std::endl; //dispose context.Dispose(); }
結果
環境はMacBookAir(i7) / v8(リビジョン: 10833)
500*1000を100回試行して平均をとる。
途中、後置きと前置きの実行順を入れ替える。
prefix-test : compile = 95 prefix-test : execute = 11551 suffix-test : compile = 93 suffix-test : execute = 12158
あれぇ?だいたいこんな感じで有意差は微妙という結果になった。
前置きの方が微妙に早いかな?
ちなみにindex=index+1も試したけどこちらも有意差は無かったです。
ちなみに以下の傾向があった
1)
前置き、後ろ置き共に
先に実行した場合は後で実行する場合より10%程度遅くなる。
(別のコンテキスト使っているのに・・・メモリブロック確保とかしているのかな?)
2)
コンテキストを継続して
初期化ー>コンパイルー>実行ー>実行・・・
の形で実行を複数回行うと2回目以降の実行時間が倍くらいになった。
ここで、Chrome側のJITのやり方に原因があるのか、
Chromeのv8って仕様が違うバージョン?と悩んだところで。
ベンチの実装により差が出ているぽい
らしいです。
JSでi++と++iどっちが速い?
ええ、ベンチマークがどんな挙動をしているのか
調べておくのは基本ですね:p
それでも、このケースでChromeだけ後置きが早くなる理由は気になるところです。