2014年6月7日土曜日

C/C++を勉強する価値

Appleが新しい言語Swiftを発表しました。既に色々な所で紹介されていますが、C/C++好きな人間からの感想です。

Appleのハードでアプリケーションを作りたかったら、Objective-Cを覚える必要がありました。Objective-Cは面白いプログラミング言語だとは思いますが、欠点も多い言語です。
  • C言語の部分と、オブエジェクト指向言語部分で文法がバラバラ。まさに付け足した感じで見た目が綺麗でない。
  •  特にオブジェクトのメモリ管理が難関。バージョン1では自分で参照カウンタを管理→バージョン2でガベージコレクタ→ARC(std::shared_ptrのようなもの)とコロコロと仕様が変わっている。
  •  メソッド(メンバ関数)やインスタンス変数(メンバ変数)の呼出が直接呼出や関数ポインタ呼出になっておらず、動的言語っぽく関数名の文字列から関数を取得するので遅い。
AndroidはJava言語でプログラミングをするのが一般的ですが、Javaに比べて扱いにくい言語であると思われます。意外に思われるかもしれませんが、 AndroidよりもiOSの方がアプリケーションのクラッシュ率は高いという報告が多いです。たぶん、これはObjective-Cというプログラミング言語のせいなのではないかと私は思っています。
 
これらを解決するための新しい言語がSwiftなのでしょう。Objective-Cに対してC言語のプログラムとソースレベルで混在できないという問題がありますが、それは大したことではないですし、iOSのアプリケーションは作りやすくなるかと思います。これからiOS用のプログラミングをする人にはSwiftを勉強したほうがいいでしょう。


さて、Swiftと言う言語ですが、パッと見た目はfuncやvarやletという予約後からJavascriptに似ていると私は思いました。また関数の返り値の型の指定はC++11っぽいと。しかし、実際にはRustという言語の影響が大きいようです。RustはMozillaによってC++の置き換えを狙って作られた言語で、C++に似た文法を採用しています。実際、Rustの言語仕様を見てみると、fn, letなどかなり似ており、RustからC++っぽい::などを除いたのが、Swiftと言う感じもします。

また、Haskelの利点でも言われていますが、「変数の値の変更がバグを産む」と言われています。C++でもconstキーワードがC言語に比べて重視されているのと似ていますが、letのような値を変えられない変数の定義を主役にするのが、なかなか面白いです。

また、Appleの発表ではObjective-CよりSwiftの方が、作成されたアプリケーションの方が速いとのことです。これは以下が考えられます。
  • id型のようななんでも型を無くして、型を型推論(C++11のautoと同じ)で簡単に型を付けられるようにしたこと。
  • letの値は変更できないことが保証されるため、最適化がしやすい。
  • メソッドの呼出方法が変わった(推測)
C/C++に比べると遅いのかもしれないですが、文法的にはJavascriptのような動的言語っぽく、C/C++やRustに比べてとっつきやすそうで、コンパイルできる言語としてはなかなか良い言語だと思いました。

しかし・・・

結局、AppleのAppleによるAppleのための言語というのが私には引っかかります。これからObjective-Cはどうなっていくのでしょう?APIであるCocoaがObjective-Cベースのため、簡単に消えることはないでしょうが、最終的には消えていくことになるような気がします。

RustもSwiftのように良いプログラミング言語ですが、知名度が低いのは言語を作った所の知名度のためでしょうし、Appleが廃れたときにはSwiftも廃れそうです。

Javaのように言語仕様の著作権を、その作成元のOracleが主張するような世の中では、Swiftが他のプラットフォームに移植されるとも考えにくい気がします。

その点、C/C++は歩みが遅かったり、色々と問題もありますが、ISOに承認されて誰でも使用できるプログラミング言語であることが利点だと思いました。Objective-Cと違い、SwiftはC言語を前提にはしておらず、また良い言語だとは思いますが、まだまだC/C++を勉強する価値はあるのでは無いかと思います。




おまけで同じく発表された3D APIのMetalの話。AMDのMantle、MicrosoftのDirectX12と、今までの3D APIに比べてハードよりの3D APIが話題になっている。今まで使用されていたOpenGLはMantle並にオーバーヘッドが少ないとのことなので、Metalはどこが違うのかと思ったら、Metalは更にハードよりで、PowerVR6に特化しているらしいです。Metalは従来より10倍速いと言っていますが、「ドローコールが」という書き方なので怪しく感じます。ゲーム全体の速度としてどうなるのだろうか?

iOSは画面サイズが固定化されているため、画面サイズの違う端末を作りにくい。iPadでは画面2分割案も出ていましたが、画面サイズが自由なAndroidやWindowsに比べて、かなり難しいでしょう。Appleの環境ではプログラミングもどんどん固定化されていくのでしょうね。

2014年6月1日日曜日

EeePC 901-X に Android-x86-4.4-RC2を入れてみた

前回に引き続き、元、Windows XPのNetbookパソコンであるEeePC 901-Xの活用方法です。

今回はAndroid 4.4(Kitkat)のx86版(普通のパソコン用)をインストールしてみました。Androidは、パソコンやスマホやタブレットなど全てのインターネット端末の中では、シェアNo.1の地位となっています。

そのAndroidが、普通のパソコンで使用することができます。 Windows XPの置き換えには最適かもしれません。


AndroidのLive USBの作成



まずは、AndroidのisoイメージをAndroid-x86のサイトからダウンロードします。2014年5月26日現在は、android-x86-4.4-RC2.isoが最新です。

ダウンロードが終わったら、LinuxやMac OS Xの場合は、ddコマンドでイメージをUSBメモリに書きます。

 $ sudo dd if=android-x86-4.4-RC2.iso of=/dev/sdX

/dev/sdXは、ドライブの番号で、実際は/dev/sdbや/dev/sdcなどです。

 Windowsの場合は、Win32 Disk Imagerを使うと良いと思います。使い方については過去の記事でも書いていますので、参考にしてください。


Android のインストール



Fedora 20の時と同様にUSBメモリからSDカードにインストールしてみます。USBメモリからの起動方法やSDカードからの起動方法はその記事を参照してください。


Live USBから起動をすると、メニューが表示されますが、一番上の「Live CD - Run Android-x86 with installation」を実行すると、インストールせずにAndroid-x86を試すことができます。




「Installation - Install Android-x86 to harddisk 」を選ぶとSDカードにインストールができ、次の画面に進み、インストールする場所を選択します。


「Flash Reader」となっているのがSDカードなので、それを選択します。ここで「FAT32[LBA]」となっているのを確認してください。次にフォーマットを選択します。



ここでは、「Do not format」 を選択してください「ext3」を選んだらSDカードが異常になり、再度SDカードのフォーマットが必要になってしまいました。注意してください。

その他、「boot loader」 はSDカードからの起動の設定と思われますので「Yes」とします。


「/system directory as read-write」に関しては、ディスクのスペースとインストールの時間が遅いとのことですが、気になるほどでは無いので「Yes」とします。



「user data」に関しては、大きいほうが良いと考え、初期値を変更して、最大値としまいた。



これらの設定が終わればインストール完了です。Rebootして、SDカードから起動するようにしてください。




EeePC 901-XでのAndroidの使用感(vs Fedora 20)



画面の動作のスムーズさについては、非常にスムーズです。Nexus 5などに比べても遜色ないスムーズさだと思います。しかし、Antutu Benchmarkでの結果は7660とかなり低いです。


それと動画に関しては、やはり動画再生支援がハードにないため、非常に遅いです。

操作に関しては、マウスカーソルを動かしてのタップと二本指でスクロールができます。ロングプレスとフリック操作はダブルタップ後に長押しや移動操作をすることでできますが、少々むずかしいです。ピンチイン・ピンチアウトは出来ないです。

WiFiは問題なくネットをすることができます。サスペンドは復帰がなぜか遅い問題があります。電源キーで電源を切ることができないので、ステータスメニュー内にあるPOWER OFFで電源を切ります。Fn+F?キーでの音量上下、光量上下もできます。前面カメラや音声入力も問題ありません。

Google Playも使用することができます。Google PlayからGoogle 日本語入力をインストールすると日本語入力も問題なく出来ました。それと、アプリによって画面が縦表示に勝手に変わり、元に戻せなくなるので、画面回転制御などのアプリをインストールすると良いです。

通常のAndroidはARM製CPUであり、x86版でアプリが動作するかが気になりますが、PC Watchでの記事によると「Houdini Binary Translator」 という仕組みで90%のソフトウェアが動くことになっているとのことです。

GPSなど機能が元々ない部分についてはどうしようもないです。その他、いくつか問題があり、Fedora 20に比べて少し不安定な気がしました。
  • アラームが動作しない。
  • アプリケーションリストの画像が表示されない。
  • 前面カメラで撮った画像が表示されない。
  • スリープから復帰しないことがある。
  •  ベンチマークソフト、Quadrant Standard Editionは不正終了してしまう。

個人的な感想ですが、EeePCではタッチパネルがなくフリック操作が難しいことや、Fedora 20では右クリックや中クリックが使え、ブラウザがフル機能で使えることを考えると、ブラウザ中心で使用するとなるとEeePCでは速度が遅くてもFedora 20の方がAndroidより便利かと思いました。

Androidならではのアプリも多いので、使いたいアプリが動くのであれば、EeePCをAndroid機にしてしまうのも良いかと思います。

2014年5月25日日曜日

EeePC 901-X にFedora20を入れてみた

私はEeePC 901-Xを持っています。Netbookと言われるPCの先駆け的存在のPCです。EeePCはLinuxモデルがあり、名前通り元々は単に安くネットが出来るPCを目指していました。

しかし、残念ながら日本ではLinuxモデルは出ず、当時はもうWindows Vistaが出ていましたが、Microsoftの戦略でNetbookに安くWindows XPのライセンスを付けることで、Netbookは単に安価のWindows PCと化して行きました。

さらに、EeePCはSSDを積んでいましたが、これもMicrosoftの戦略でSSDの場合は16GBまでのみ(しかもHDDの場合は160GBまで)という制限が付けられたせいで、NetbookはSSDから遠ざかり、HDDの製品ばかりになってしまいました。

たまにAppleがNetbookはiPadが殺したというような台詞を言っていますが、個人的にはその頃には、本来の意味のNetbookは既にMicrosoftによって殺されていたと思っています。

前置きが長くなりましたが、そのEeePC 901はバッテリーをAmazonで購入し直すことで、まだ現役で使うことができています。ASUSの純正でないので、色は多少違いますが、気にならない程度です。


EeePCもWindows XPであり、Windows XPはサポート機嫌が切れたため、Linuxを入れるのも良いかと思います。そのような記事もあります。

よく古いPCにはFedora 20などのディストリビューションは重すぎるといい、上記の記事でもXubuntuを入れていますが、私はGnome 3が好きなので、Fedora 20を入れてみました。



USBメモリにFedora 20のLive DVDを入れる



EeePCは、CD/DVDドライブが付いていないため、USBメモリからインストールするのが楽です。またEeePCのCPUであるAtom N270は64bitには対応していないため、32bitをインストールする必要があります。

Fedoraのページからデスクトップ版32bitのISOイメージをダウンロードします。

その後、Live USB Creatorを使用して、USBメモリに上記ダウンロードしたISOイメージを書き込みます。fedoraのPCがすでにあれば、パッケージにあるので、sudo yum install live-usbcreatorでインストールできます。Windows版はここでダウンロードできます。

私はUSBメモリには4GBのものを使用したのですが、最初はUSBメモリから起動せずに困りました。もしかするとLinuxでもfdiskで出来たのかもしれませんが、Windows Vista以降にあるdiskpartでフォーマットし直すことでUSBメモリから起動できるようになりました。Live USB Creatorで書き込む前にUSBメモリをフォーマットをしなおしたほうが良いです。

フォーマットが終わったら、「Browse」ボタンでダウンロードしたisoファイルを選択し、「Create Live USB」ボタンを押せば、Fedora20が動くLive USBが出来上がります。



USBメモリからSDカードにインストールする


EeePC 901-Xは、SSDとして4G+8Gの領域があるのですが、ここにインストールせずともSDカードにインストールすることが出来ます。

SDカードは遅いですけど、SDカードなら飛び出さないので邪魔にもならず、またWidows XPの領域を壊す必要もなく、容量も大きくとれるという利点があります。

今回は16GBのSDカードにFedora20を入れました。

SDカードとLive USBを入れてEeePCの電源を入れます。入れたらEscキーを連打します。すると下記のような画面になるので、「USB:Single Flash Reader」で無い方のUSBである「USB:FLASH Drive SM_USB20」(USBメモリの製品によって異なります)を選択します。



するとFedora 20が起動します。起動したら「Try Fedora」と「Install to Hard Drive」の選択画面が出ますので、後者を選んで、インストールを開始します。詳細は以前のFedora 20のインストールの 記事を参考にしてください。

注意するのは、HDDの選択時です。3つ表示されるので、SDカードを入れてある「Single Flash Reader」にチェックが入るようにしてください。



インストール自体は30分程度で終わります。ただし、sudo yum updateで全てをアップデートするのには8時間近くかかりましたので、ご注意ください。

インストールが完了した後には、SDカードから起動をするように設定をします。EeePCの電源を入れるときに、F2キーを連打して「BIOS SETUP UTILITY」を起動して、左右のカーソルキーで「Boot」を選択して上下のカーソルキーで「Hard Disk Drives」を選択してEnterキーを押します。

この画面で、「1st Drive」が[USB:Single Flash R]となるように+, - キーを押してください(+, - キーキーを入力するには、Fn+F11で、Num LkがONの状態にし、「れ」と「め」のキーが「-」と「+」になります)。


これで、F10キーでSave and Exitをすれば、電源を入れた時にSDカードから起動するようになります。


EeePC 901-X + Fedora 20 の使用感


起動と終了にはかなりの時間がかかりますし、ログオンにもかなりの時間がかかります。電源を入れてから使えるようになるまで、2分ぐらい(ログオンで1分ぐらいかかる)でしょうか。さらにブラウザのFirefoxを起動するのに40秒程度かかります。とてもサクサク動くとは言えないでしょう。

しかし、動かしはじめてからは結構問題なく使えます。Gnome 3のアニメーションも思ったよりスムーズに動きます。


メモリの使用量もOS起動直後で260MBほどと、Windows XPとそれほど変わりませんし、電力量もワットチェッカーで測った所、13W程度とこちらもWindows XPより少し多いぐらいという感じでした(電力を使わない使い方なら4時間ぐらいは使えそう)。

動画は再生支援がないためきついですが、Youtubeの低画質の動画がなんとか見られる程度でした(Google ChromeだとHTML5再生になるが、Firefox + Adobe Flashの方が良かったです)。文字入力などはmozcでもそれほど遅いとは感じないです。

WiFiでの通信やFnキーと組み合わせる光量や音量の変更などハードの関係は全く問題なさそうでした。しかしサスペンドはサスペンドに入るまでが、30秒近く掛かってしまいます(復帰は速いです)。

それと、タッチパッドは初期設定ではタップができないなど不便なので、マウスの設定画面からONして使っています。EeePC 901-Xではマルチタッチに対応しているので、二本指でのスクロールもできますし、また二本指タップでマウスの右ボタン、三本指タップでマウスの中ボタンという使い方も問題なく出来ました。

特に気になったのは、SDカードが悪いのか、yumでのインストール作業が非常に遅いこと程度です。

サクサクな動作のために機能が低いディストリビューションを選ぶのも悪くはないですが、 個人的にはこの程度の速度でフル機能のLinuxが使えるのであれば、満足できるかと思いました。せっかくのEeePC、Windows XPのサポート期限終了で引退させるのではなく、こういう使い方も良いのではないかと思います。

2014年5月5日月曜日

C/C++ データの扱い その1

何度かコンピュータの基本はCPU+メモリ+I/Oという話をしましたが、前回までの「処理の流れ」がCPUの部分で、今回のデータがメモリの部分と言うことになります。

データの保存場所とスコープと種類


データを扱う時には以下の4つを考える必要があります。
  • 保存場所
  • スコープ
  • 初期化と終了処理
  • 種類(型、タイプ)

保存場所


保存場所には、大きく動的なデータと静的なデータがあり、更に動的なデータにはレジスタ、スタック、ヒープがあり、静的なデータには、ゼロで初期化されたデータ、ゼロ以外で初期化されたデータ、読み取り専用データの種類があります。
  • レジスタ(動的)
  • スタック(動的)
  • ヒープ(動的)
  • ゼロで初期化されるデータ(静的)
  • ゼロ以外で初期化されるデータ(静的)
  • 読み取り専用のデータ(静的)
静的というのはコンパイル時に作られるデータで、プログラムが実行されてから終了するまで、メモリに存在するデータです。動的というのはプログラム実行中にメモリに作ったり削除したりできるデータです。

一般的に静的なデータの方が速度が早く、動的なデータの方がメモリを割り当てたり解放したりする必要があるため、速度が遅いです。しかし、メモリを常に使っていることになるのでメモリ使用の効率が良くないです。

ゼロ以外で初期化されるデータはコンパイルしてできた実行ファイルに含まれ、プログラムが実行時にと実行ファイルからメモリに読み込まれます。ゼロで初期化されるデータは実行ファイルに含まれず、実行時にメモリ領域が作成され、ゼロに初期化されます。

読み取り専用のデータは、ゼロ以外で初期化されるデータとほぼ同じです(OSのセキュリティ機構で書き込みが出来ないよう保護することが可能です)。

動的なデータとしては、速度は一般的にレジスタ > スタック > ヒープ となっています。 その代わり速いほど大きなデータは扱えません。

レジスタはCPU上にある記憶領域で、通常のメモリとは異なります。大抵はメモリ上のデータをCPU上のレジスタにデータを読み込んでから計算します。レジスタはCPUによって数が決まっており少量です。

スタックは、プロセスやスレッド毎に作成されるメモリ領域です。そのサイズはOSやコンパイラによって違います。スタックに複数のデータが追加されたり、削除されたりしますが、順番が決まっています(最後に追加されたデータから削除しないといけない)。また、スタックのサイズは一度プロセスなどを作成してから変更できず、そのサイズを越えて追加が行われるとメモリ違反エラーになるなどの問題が起きます。

このスタックには通常のデータだけでなく、関数の引数やリターンアドレス等のデータも一緒に含まれます。

ヒープはOS等に必要なメモリサイズを要求して、取得されるメモリです。大きいサイズが取得でき、またサイズを変えることができるなど自由度が高いです。大きすぎる場合には取得時にOSがエラーを返すので、エラー処理を記述する必要があることや、解放が必要なため、メモリーリークが起きやすいなど、扱いが難しい部分もあります。


スコープ


保存場所がコンピュータの仕組み的な区別なのに対して、スコープはプログラミング手法での扱い(読み取りや書き込みのアクセス手段)の区別です。例えば以下があります。
  • グローバル(どこからでもアクセス可能)
  • 同一ファイル内からのみアクセス可能
  • 関数内からのみアクセス可能
  • 同じ名前空間内からのみアクセス可能(C++のみ)
  • 同じクラスからのみアクセス可能(C++のみ)
データのスコープが広い(色々な場所からアクセス可能)であるほど、使い勝手はいいです。しかし、その分プログラムの問題が起きやすく、また問題が起きた時に原因を見つけるのが大変です。そのため、少しでもデータのスコープを狭くすることが重要です。


初期化と終了処理



C/C++言語ではデータに値が入っているとは限りません。これはどんな値が入っているかわからないという意味で、不定値と言います。

C/C++言語では初期化を行う必要があるのですが、暗黙的に初期化が行われたり、明示的に初期化が行われたり、その書き方が様々です。

他の言語では不定値を持つことは少ないのですが、C/C++言語は速度を犠牲にしないために、初期化のタイミングを自由にできるようにするために不定値を持ちます。しかし、不定値は原因が特定しにくい不具合を生みやすいです。

終了処理も場合によっては行う必要があり、行うのを忘れると不要になったメモリがずっと使われている状態(メモリリークと言う)の問題を起こすことがあります。


種類(型、タイプ)



C/C++言語は、データの種類であるデータ型を意識してプログラムする必要があります。プログラミング言語によっては、型を意識することが少ない言語もあるのですが、C/C++言語は強い型付けがある言語です(C言語よりC++言語の方が強い)。

型は、例えば整数なのか、画像なのか、どのようなデータを持っているのかを表すという目的と、データで行われる処理が正しいかをチェックする目的があります。

型により適切なデータ量のメモリを使用することになります。

それと、例えば、整数と画像を足し算すると言うような処理は異常ですが、そのような間違えた処理が行われた場合に、静的に(コンパイル時に)チェックを行い、間違いを知らせることができます。


色々なデータの宣言方法



C/C++言語では、型を宣言することで、データを使用することが出来るようになります。その宣言方法で、「保存場所/スコープ/初期化と終了」が変わってきます。

以下にその宣言方法の例と、「保存場所/スコープ/初期化と終了」について記述します。型については数が多いので、一般的な整数型であるint型を使用して、その他の型については後述とします。

int              dataA;
int              dataB = 1;
static int       dataC;
const int        dataD = 2;
const static int dataE = 3;

void function()
{
        int              dataF;
        int              dataG = 4;
        static int       dataH;
        const int        dataI = 5;
        const static int dataJ = 6;
        registor int     dataK;
        auto     int     dataL;

        dataG += 1;

        {
                int dataM;
        }

        int dataN;

        for(int dataO = 0; dataO < 10; dataO++) {


        }

        int *dataP = (int i*) malloc(sizeof(int));
        int *dataQ = new int;

        free(dataP);
        delete dataQ;
}


dataAからdataQまで値の定義をしていますが、これらは「保存場所/スコープ/初期化と終了」が以下のようになります。

保存場所スコープ初期化と終了備考
dataA静的グローバル0で初期化
dataB静的グローバル指定した値(1)で初期化
dataC静的同一ファイル内0で初期化初期値指定も可。C++ではstaticでなく名前空間(namespace)を奨励です。
dataD静的(読み取り専用)グローバル指定した値(2)で初期化C++では初期値指定必須。
dataE静的(読み取り専用)同一ファイル内指定した値(3)で初期化C++では初期値指定必須。C++ではstaticでなく名前空間(namespace)を奨励です。
dataFレジスタまたはスタック同一関数内不定値
dataGレジスタまたはスタック同一関数内指定した値(4)で初期化
dataH静的同一関数内0で初期化
dataI静的(読み取り専用)またはスタック同一関数内指定した値(5)で初期化C++では初期値指定必須
dataJ静的
(読み取り専用)
同一関数内指定した値(6)で初期化C++では初期値指定必須
dataKレジスタまたはスタック同一関数内不定値初期値指定も可。レジスタ優先だが、あまり意味はないので通常registorは使用しない。
dataLレジスタまたはスタック同一関数内不定値初期値指定も可。あまり意味はなく、通常autoは使用しない。C++11以降はautoは別の意味で使用される。
dataMレジスタまたはスタック}で閉じるまで不定値その他、初期値指定、static指定など関数初めの宣言と同じ指定も可。
dataNレジスタまたはスタック同一関数内不定値C95以前は、関数や演算以降は宣言不可。C99以降、C++ではその他、初期化、static指定など関数初めの宣言と同じ指定も可。
dataOレジスタまたはスタックforの{}内指定した値(0)で初期化C95以前は、for内で宣言不可
*dataPヒープdataPと同じ不定値。free()関数で終了するまでメモリを使用する。
*dataQヒープdataQと同じコンストラクタで初期化。deleteで終了するメモリを使用する。C++のみで可能で、C言語では不可
dataPレジスタまたはスタック同一関数内指定した値(maloc()関数の返り値)で初期化データの先頭アドレスが保存される。ここではdataMのように指定したが、グローバルにすることもstaticやconstにすることも可能。
dataQレジスタまたはスタック同一関数内指定した値(newの返り値)で初期化データのアドレスが保存される。ここではdataMのように指定したが、グローバルにすることもstaticやconstにすることも可能


  • 静的なデータは初期化されると考えてください。
  • constは定数の意味ですが、C/C++言語では読み取り専用と同等の意味で考えて良いです。
  • レジスタかスタックかは通常はコンパイラが決めるため、指定の必要はないです。
  • ヒープに関してはポインタの知識が必要ですが、ポインタについての説明は後日予定です。
  • ヒープ以外はスコープから抜けるとメモリが解放されますが、ヒープは明示的にメモリを解放する必要があります。
  • for分で使っている10などのリテラルの値は静的(読み取り専用)となります。
  • 同一ファイル内のスコープとするためにstaticが使われますが、意味が不明(staticは静的の意味)のため、C++では同様の目的で名前空間を使用するのが奨励です。名前空間についての説明もまた後日予定です。
  • C言語のバージョン C95以前には、型宣言の場所が先頭のみとなっており、関数や演算の後には型宣言が出来ません。dataMのように{}内であれば、その先頭で型宣言が可能です。



2014年5月4日日曜日

日経LinuxをPDF版に

今まで日経Linuxは雑誌を買っていたのですが、先月からPDF版を買うことにしました。

日経Linux「1冊まるごと!PDF版」2014年5月号

やはり雑誌は場所を取るので、その点電子書籍はいいですね。しかし電子書籍はDRMが掛かっていたり、特定のソフトウェアで無いと読めなかったりと不便な物が多くて残念です。日経Linux PDF版でその点は安心です。なお、サイズは98.3MBと少々大きいです。

Fedora 20 ではGNOMEのソフトであるドキュメントビューアー(Evince)で問題なく読むことができました。
設定で「見開きページ」と「奇数ページを左に」にチェックを入れると、2ページにまたがったページも見やすいです。

Nexus 5(Android)では、デフォルトのQuickofficeでも見ることは出来ますが、EbookDroidというアプリをGoogle Playからインストールして、それで読んでいます。

Android4.4(KitKat)からの機能であるステータスバーやシステムUIを隠して全画面表示する表示にも対応しており(Common Settingsより設定可能)、また(以前に要らないと書いたのですが)1920×1080のフルHD表示のためか、一ページを全画面に表示しても十分読めます。しかし、やはり拡大したいこともあり、ダブルタップを「Quick zoom」に割り当てて、片手でも拡大しやすいように設定しました。

これで電車に乗っている時など暇な時間にも読めるようになり、便利になりました。

2014年4月30日水曜日

低価格SIMフリー端末 freetel を使ってみた


低価格で節約してスマホを運用するためのMVNOがテレビや新聞などで紹介されてくるようになってきました。しかし、残念ながらSIMフリーの端末はあまり出ていないように思われます。なかなかSIMフリーの時代が来ないものです。

現在のMVNOはDTI Serversman SIM LTEなどもすべてdocomoの回線を使用しているため、docomoの端末でも低価格のメリットは得ることができると思いますが、SIMフリー端末が出ないと最終的に真に自由な競争による低価格化や発展はない気がします。

2014年4月現在に日本で買えるSIMフリー端末は以下だと思います。低価格を基準にしているので種類が多いのは安い物を選んでいます。また、一応タブレットでなく、電話の出来るスマホと言える端末のみです。もちろん技適マークが無い海外製のスマホは、安くても日本で使ってはダメなので、含みません。

使ったことが無い端末がほとんどなので、参考には全くなりませんが、簡単に知っている利点も書いてみました。

端末価格利点
freetel11,967円 (Amazon)日本でのサポートが非常によく、一番安い。2つのSIMが使えるので海外で使うのに便利。バッテリー交換可能。
ASUS Fonepad 7 TABLET 27,243円 (Amazon)量販店で買える?7inchと画面が大きい。
PolaSma27,999円(税抜)トイザらスで購入できる。子供用で、子供にも安心して使える機能が用意される。 2つのSIMが使える。
Nexus 5 16GB40,937Google謹製。Androidのアップデートが早い。開発者向けな情報が多い。約5inchの画面にFullHD(1920x1080)。
iPone 5c 16GB57,800日本では一番売れているApple製品の中では一番安い。




freetelを使ってみた


上記の中で圧倒的に低価格なfreetelを買ってみました。もちろんNexus 5の方が全てにおいて良いですし、安物であることには変わりないでしょう。しかし、どの程度の事をスマホに求めているかにもよりますが、あまりスマホに依存していない私としては、これで必要十分な機能を持っているのではないかと思いました。




最初から入っているソフトウェアは以下のみでした。Nexus 5よりも少ないです。
  • カメラ
  • カレンダー
  • ギャラリー
  • ダウンロード
  • タスクマネージャー
  • ブラウザ
  • メール
  • メッセージ
  • ユーザー
  • 音楽
  • 音声レコーダー
  • 検索
  • 時計
  • 設定
  • 電卓
  • 電話
  • 動画プレイヤー
  • GMail(マイアプリにも表示)
  • Google設定
  • Google日本語入力(マイアプリにも表示)
  • Playストア
個人的には音声での検索(Google Now)、地図はAndroidを使いやすくするには必須と思ったため、以下を追加してインストールしました。音声でのアラームや通知の設定や、電車の時刻の検索もできますし、ナビももちろん使えます。
  • Google検索
  • Googleマップ
その他、Youtubeも入れています(ブラウザで見るより読み込みが速く思えたため)。優先インストール先がSDカードになっているので、だいたいこの時点で141MB使用、79MBの空きとなっています。SDカードに入らないアプリを使用しない工夫が必要そうです。しかし、大量に入れたいアプリがある人(特にゲームなどをする人)でなければ大丈夫でしょう。

スペック表などで分かりにくい欠点としては以下があります。
  • USB接続用コード、イヤホンが専用となっている。
  • 裏蓋が外しにくい。
  • 内蔵メモリが少ないので、どのアプリを入れるか考える必要がある。
  • 液晶の視野角が上下方向に非常に狭い。
  • バッテリーがあまり持たない(持って1日程度)。
  • 同じDTI ServersMan SIM LTEを使用してもNexus5より遅い?(対応周波数が狭いため?)。Youtubeなどを見ると遅くて止まるのですが、その頻度が高い気がする。なお、DTI ServersMan SIM LTEの設定はプリセットで入っているので、設定はNexus 5より簡単で選ぶだけです。
  • GPSのつかみが非常に遅い(いつまでも終わらない)。ただし、A-GPSを使えばだいたいの位置はすぐに表示される。
  • ピンチイン、ピンチアウトが縦方向で効かない。
  • タッチの反応が少し下側にずれる(ボタンなどを気持ち上側を押す必要がある)。
  • 付属のSDカード(8GB)がすぐ(2週間程度)で壊れる。 
  • ボタンを押す音が少々うるさい。
たくさん悪い所を書いてしまいが、逆に言えばこれ以外は普通によく出来たAndroid端末で、速度もそれほど悪くはありません。性能と価格を考えるとこれで十分と思えてしまいます。今後、もっとこういう端末が増えて、更に安く高性能になって行って欲しいです。


freetelでAndroidのソフトウェア開発


freetelは性能的には、Nexus 5よりかなり劣りますが、逆にそれがAndroidのソフトウェアのテストにも使えて良いかもしれません。

Windowsでは、freetelをUSB接続したときに表示される「USB settings」の画面で、「USBバーチャルドライブ」を接続したときに入っているファイル"ADB.RAR"を解凍して、adb_install.batを実行すると、開発できるようになるようです。

Fedora 20では、下記を実行することでfreetelがUSB接続で認識され、Eclipseからプログラムを実行できるようになりました。最後のadb devicesコマンドにおいてListにxxxxxxxx(この部分は数字です)が表示されていれば大丈夫だと思います。Ubuntuでもsystemctl以外は大体同じで大丈夫だと思います。

$ echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="1782", ATTR{idProduct}=="5d04", MODE="0666"' > 51-android.rules
$ sudo cp 51-android.rules /etc/udev/rules.d/
$ sudo systemctl restart systemd-udevd
$ echo 0x1782 > ~/.android/adb_usb.ini
$ adb kill-server
$ adb devices
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
List of devices attached 
xxxxxxxx        device


前にNexus 5 で作成したプログラムをfreetelで実行すると以下のようになります。


C/C++でOpenMPを試してみる

OpenMPとは?


OpenMPとは、並列プログラミング用の 機能で、C/C++, Fortran で使用できます。並列プログラミングには色々とありますが、以下の様な利点があります。
  • 通常のプログラムからOpenMPを使用するように書き直しやすい。
  • ハード(CPUのコア数)に依存しないプログラムが書きやすい。
  • Visual Studio(Pro2005以降), gcc(4.2以降), インテルコンパイラ(V9以降)などある程度マルチプラットフォームで使用できる。

逆に欠点としては以下があります。
  • 記述に#pragmaディレクティブというコンパイラ専用の機能の記述に使用する方法を使用するので、プログラムが見にくくなる。
  • パフォーマンスは特にコア数が増えると今一歩のところがあるらしい。
  • 現在OpenMP4.0まで出ているが、Visual StudioはOpenMP2.0まで、LLVMは現在OpenMP3.1に対応中と対応に差がある。


OpenMPをC/C++で使うには?


OpenMPが使用できるかどうかをチェックするためには、_OEPNMPが定義されているかどうかで判別します。本来ならOpenMPの機能を使う部分はすべて

#ifdef _OEPNMP
#include <omp.h>
#endif

と書き、無視されるようにした方が良いでしょうが、今回の例では省いていますので注意してください。

#pragmaは認識されなかった場合は無視されるので、_OPENMPでの#ifdefは必要ないです。omp_get_thread_num()関数などのOpenMP用の関数はコンパイラが対応していても、OpenMP用のコンパイルオプションを付けないとエラーになります。

コンパイル時には、 VisualStudioの場合、/openmp をつけてコンパイルすれば良いようです。gccの場合は、-fopenmp をつければ良いです。今回はgccで動作確認しています。MinGW環境でも、動作します。

omp_set_num_threads()関数などOpenMPの関数を使用する場合にはヘッダーファイルとしては"omp.h"をインクルードします。


並列実効領域構文 ー parallel構文


OpenMPを使うときには、まずはprallel構文で並列実効領域の指定をします。以下のようになります。

#include <stdio.h>

int main()
{
        #pragma omp parallel
        {
                printf("parallel\n");
        }
        return 0;
}

#pragma omp というのが、OpenMP の指令であることを示しています。-fopenmpを指定しない場合は無視されます。

parallel構文では、{}で括られた部分が、CPU毎にスレッドが作られ実行されます。例えば2つのコアのCPUの場合は、二回printfの実行が行われます。

より詳細に説明すると以下のようになっています。
  • { は #pragmaと同じ行には書けません。実行が一行のみの場合は {} は省略できます。
  • スレッド数は指定することも可能です。方法はいくつかあります。
    1. 環境変数 OMP_NUM_THREADS に数を指定する。
    2. #pragma omp parallel num_threads(数)と後ろに指示句num_threadsをつける。
    3. omp_set_num_threads(数)関数で指定する
  • omp_get_num_threads(数)関数でスレッドの数を取得したり、omp_get_thread_num()関数でスレッド番号(0,1,...)を得ることで、スレッドごとの処理をすることができます(ただしあまり必要ないです)。


ワークシェアリング構文1 ー for構文



for構文は、後に続くfor文を自動的にスレッドの数に分解して実行してくれます。


#include <stdio.h>
#include <omp.h>

int main()
{
        char data[100];
        int i;

        #pragma omp parallel
        {
                #pragma omp for
                for (i = 0; i < 100; i++) {
                        data[i] = 0;
                }
        }
        return 0;
}


parallel構文で作成されたスレッドが2つの場合、i を 0~49, 50~99 に分けて data[i] = 0; を実行します。

parallel構文とfor構文を合わせて以下のように書くこともできます。

        #pragma omp parallel for
        for (i = 0; i < 100; i++) {
                data[i] = 0;
        }

データをどのようにスレッドで分割するかを指定することもできます。通常は static ですが、dynamic や guided が指定できます。指定は、#pragma omp for schedule(タイプ)とします。
  • static - データをスレッド数で分割して実行します。
  • dynamic - 終わったスレッドからデータを取ってきて実行します。
  • guided- static + dynamic。徐々にstatic分を減らしていく。最も効率的らしい。
  • runtime - 環境変数OMP_SCHEDULEに従って、static,dynamic,guided を切り替え。

以下は、スレッド番号が小さいスレッドほど時間が掛かるようにして、スケジュールの違いを分かるようにしたつもりのプログラムです。

#define DATA_SIZE 20
#include <stdio.h>
#include <omp.h>
#include <unistd.h>

void exec_task(int i)
{
        int id;

        id = omp_get_thread_num();
        printf("%d - start %d\n", id, i);
        usleep(i * 100000);
        printf("%d - end   %d\n", id, i);
}

int main()
{
        int i;

        #pragma omp parallel num_threads(2)
        {
                #pragma omp for schedule(static)
                for(i = 0; i < DATA_SIZE; i++) {
                        exec_task(i);
                }
        }
        return 0;
}

staticの場合には、0番のスレッドで0〜9のデータの処理を行い、1番のスレッドが時間が10〜19のデータの処理を行っていますが、1番スレッドで時間がかかっているのがわかると思います。

staticをdynamicにすると、1番と2番でほぼ交互にデータを分けて処理することがわかると思います。

staticからguiedeにすると、15,16,17のデータは0番のスレッドで処理されていることがわかると思います。
 
どのscheduleも(static, 3)などとチャンクサイズを指定することができます。チャンクサイズでデータの反復数を変更することができます。

ワークシェアリング構文2 ー section構文



複数の処理を平行して実行します。例えば以下のように使います。

int main()
{
        #pragma omp parallel
        {
                #pragma omp sections
                {
                        #pragma omp section
                        function1();
                        #pragma omp section
                        function2();
                        #pragma omp section
                        function3();
                }
        }
        return 0;
}

スレッドの数が二つの場合、function1()とfunction2()を同時に実行します。先に終わった方のスレッドでfunction3()を実行します。

for構文と同じく #pragma omp parallel sections と省略可能です。


共有データとプライベートデータ


前述のワークシェアリング構文1 - for構文の最初の例で i と data[100] の扱いが気になった方は見えますでしょうか?

data[100] はすべてのスレッドで共有のデータになっていますが、i はスレッドごとに別のデータとなります。

基本的に、#pragma omp parallel より前で指定されたデータはすべてのスレッドから参照される共有データ、後で指定されたデータはスレッドごとのプライベートデータとなります。しかし、for文のループインデックスは例外とみなされ、プライベートデータとなります。

共有データとプライベートデータを指定することもできます。

        int a = 1, b = 2, c = 0, d;
 
        #pragma omp parallel private(a), firstprivate(b), shared(c, d)


上記の場合、a, b はプライベートデータと c, d は共有データとなります。

a と b の違いは、b はスレッド開始時にすべてのスレッドで値が初期化されます。a の場合はスレッドによっては不定値となります。

最後のスレッドのプライベートデータをparallel終了時に取得するlastprivate() 構文もあります。例えばfor文のiの値をOpenMPの処理から抜けた後に使用する場合に #pragma omp parallel for lastprivate(i) とします。

また reduction というものもあり、各スレッドにおいてはプライベートとして扱うけれど、最終的にそれらをまとめる変数を指定できます。

reduction(演算または組み込み手続き : 変数)というような指定になり、演算または組み込み手続きには + や.and. や max など様々な方法で値をまとめます。

例えば、和を求めたい場合には、以下のようになります。


#include <stdio.h>

int main()
{
        int i, sum;

        #pragma omp parallel reduction(-:sum)
        {
                #pragma omp for
                for (i = 1; i <= 1000; i++)
                        sum = sum + i;
                printf("sum = %d\n", sum);
        }
        printf("sum_result = %d\n", sum);
        return 0;
}


4コアCPUの場合は以下のような結果になり、4つのスレッドで計算された値が最終的にたされていることがわかります。

sum = 218875
sum = 93875
sum = 31375
sum = 156375
sum_result = 500500


同期構文 ー barrier同期など


parallel構文では複数スレッドが動作します。スレッドが複数動くということは、スレッド同士でデータや処理のタイミング(同期)を考える必要があります。

parallel構文を抜ける時は全てのスレッドが終了してからを抜けるようになっています。このように全てのスレッドの終了を待つのをbarrier同期といいます。


parallel構文の中では複数のワークシェアリング構文が使用できます。ワークシェアリング構文も終了時にbarrier同期が行われます。

OpenMPではワークシェアリング構文の終了時に自動的にbarrier同期が行われますが、待たないようにすることも可能で、その場合はnowait指示句を使用します。

一方、通常の演算や関数等の実行など同期が行われない物もあります。同期を取るようにするにはbarrier構文を使用します。例えば以下のようになります。

        #pragma omp parallel
        {
                #pragma omp for
                        :
                #pragma omp for nowait  ← 前のすべてのスレッドが終わってから始まる
                        :
                #pragma omp sections    ← 前の終わったスレッドから使用して始まる
                        :
                function1();            ← 前のすべてのスレッドが終わってから始まる
                #pragma omp for         ← 前の終わったスレッドから使用して始まる
                        :
                function2();            ← 前のすべてのスレッドが終わってから始まる
                #pragma omp barrier
                #pragma omp for         ← 前のすべてのスレッドが終わってから始まる
                        :                
        }

今までの例のプログラムでもprintf()を使用していましたが、排他制御がされていない関数の場合、問題が起こるかもしれません。そのような場合には、以下のように

        #pragma omp critical
        function(...);

とすれば、排他制御できます。ここで指定した関数を、別のスレッドで実行中のときに 実行しようとすると、別のスレッドの処理が終わるまで待つことになります。

critical(name)の様に名前を指定することもできます。また、{}を使えば複数行を排他制御できます。

そして、共有データの場合には、複数のスレッドからアクセスされることになり、その順番が問題になることがあります。例えばaという変数の場合には、以下のように

        #pragma omp flush(a);
        function(a);

とすることで、aの順番が保証されることになります。barrier構文と同じく、ワークシェアリング構文のfor, sections等の出口で自動的に行われますし、nowaitでは行われません。


その他、以下のような物があります。
  • #pragma omp single   ある一つのスレッドのみで実行する。barrier同期,flashあり
  • #pragma omp master 0番のスレッドのみで実行する。barrier,flash同期なし。
  • #pragma omp atmic 直後の複合代入文をatmicに行います。

とりあえず、説明はこれで終わりです。上記はOpenMP2.0までで、OpenMP3.0では omp taskが追加されたり、最新のgcc4.9で追加されたOpenMP4.0ではSIMD命令が指定できたり更に複雑な処理ができるようです。興味のある方は試してみてください。