2014年8月3日日曜日

Firefox OS Flame を試してみた。

Firefox OS 開発者向けのリファレンス端末Flameが販売されたので、購入しました。

一度Firefox OSについてブログに書いたのですが、正直な所、見に来ていただける方も少なく、世の中の感心は低く、どの程度のシェアが取れるのかは微妙だと思っています。しかし、AppleやGoogleの独裁的な世の中になってもつまらないですし、頑張って欲しいところです。auも一応オシャレなFirefox OS端末を出すようですし。



見ての通り、大きさについてはNexus 5に比べて少々小さい程度です。重さはほとんど変わらないはずですが、持った時にFlameの方がずっしりとした感触がありました。また比較すると画面が少し暗いです(どちらも明るさは自動にしています)。その分かSIMを常時入れていなかったからかバッテリーの持ちは若干良いように感じました。

レビューについては他のサイトを参考にしていただいた方が良いかもしれません。注意点としては、現在は日本語には対応していおらず、各種表示が英語であったり、日本語入力ができません(しかしブラウザなどで、日本語のサイトを見ることは可能です)。最近としては当たり前ですが、Flash Playerのサイトを見ることもできません。

また、同梱物としてACアダプタが無いので、充電はPC等に接続する必要があります。

それと、初期のままではDTI Serversman LTEのSIMの使用ができませんでした。このサイトなどを参考に設定をすることで使用できるようになりますが、現状はWindowsでは設定できないようなので注意が必要です。

なお、Fedora21では以下を行う必要があります。
  1. ここを参考にして、Andoridの開発環境を入れてadbコマンドを使用できるようにする。
  2. ここを参考にして、51-android.rulesにFlame( idVender:05c6, idProduct:9025)を登録する。
普通に接続するだけでは、adb devicesコマンドにおいて、no parmissionになり、読み込みや書き込みが出来ないです。またFirefox OSがロック解除状態でないと接続できないことに注意してください。

接続ができたら、ここよりダウンロードしたpatch-apn.shを実行すれば良いです。後はDTI Serversman LTEの設定は初めはないため、custom settingsで設定をする必要があります。

下の画面のように、APN:dream.jp, Identifer:user@dream.jp, Password:dti, CHAPを設定すれば良いです。



上の通信のアイコンでH1と表示されていれば、繋がっていると思われます。

まだほとんど使っていないのでまと外れな点もあるとは思いますが、Andorid(Nexus 5)との比較で簡単にFirefox OSとの比較して気づいたことを書きます。
  • iOSっぽくホームボタンがあるが、機械式でないため、画面ONには使用できず、Androidと同様画面ONは不便でもある。
  • Androidのような戻るキーがなく、基本的にタイトルバー上の文字の左横の「<」キーや画面右下の「^」キーを使用することになるが、 範囲が狭く押しにくい。
  • 速度については悪くないが、タッチパネルが不意に反応してしまうことも多い。
  • ブラウザでサイトを見ると、モバイルと認識されず、PC用画面で表示される。
  • Google Map(ブラウザ版)では、ピンチズームの動作が変。
以上、色々と悪い所を書きましたが、まだ熟れておらず仕方ないのかもしれません。ただブラウザで色々なサイトを見ると言う点では十分な機能を持っています。

また気づくことなどがありましたらブログを書きたいと思います。

2014年7月28日月曜日

autoconf と automake その2

前回のautoconfとautomakeのその2です。前回よりもう少しだけ難しいことをしてみます。
  • フォルダの階層分けをする
  • GTKを使う
  • 国際化(言語によって、文字列を切り替える)を行う

まずソースコードをsrcフォルダに入れて、src/main.c, src/func.h, src/func.cのフォルダに入れます。プログラムは以前にGTK+3.0用に書いたサンプルとほぼ同じです。ENABLE_NLSの部分が国際化の部分です

src/main.c
#include "config.h"
#include "func.h"
#include <gtk/gtk.h>
#include <glib/gi18n.h>

int main(int argc, char *argv[])
{
#ifdef ENABLE_NLS
        bindtextdomain(PACKAGE, LOCALEDIR);
        bind_textdomain_codeset(PACKAGE, "UTF-8");
        textdomain(PACKAGE);
#endif
        gtk_init(&argc, &argv);
        func();
        return 0;
}


src/func.h
void func.c();


src/func.c
#include "config.h"
#include <gtk/gtk.h>
#include <glib/gi18n.h>

void func()
{
        GtkWidget *dialog;

        dialog = gtk_message_dialog_new(NULL, 
                                GTK_DIALOG_DESTROY_WITH_PARENT, 
                                GTK_MESSAGE_OTHER,
                                GTK_BUTTONS_OK,
                                _("Hello!")
                                );
        gtk_dialog_run(GTK_DIALOG(dialog));
        gtk_widget_destroy(dialog);
}

この場合、Makefile.amを直下とsrc以下に2つ作成する必要があります。

Makefile.amでは、makeを行うサブディレクトリの指定をしています。またACLOCAL_AMFLAGSはaclocalを再実行するときの m4フォルダ(後述)を指定しています。

src/Makefile.amでは、AM_CPPFLAGSで、コンパイル時に渡すオプションの指定をしています。-DLOCALEDIRは国際化のためのディレクトリの指定で、@GTK_CFLAGS@はGTKを使うためのpkg-config --cflagsと同等です。amtest_LDADDはリンクするライブラリの指定です。@GTK_LIBS@はpkg-config --libsと同等になります。

Makefile.am
ACLOCAL_AMFLAGS = -I ./m4
SUBDIRS = src po


src/Makefile.am
AM_CPPFLAGS = -DLOCALEDIR=\"$(localedir)\" @GTK_CFLAGS@
bin_PROGRAMS = amtest
amtest_SOURCES = main.c func.c func.h
amtest_LDADD = @GTK_LIBS@


configure.acは以下のように書き換えます。青色の部分が前回と違う部分です。AC_CONFIG_AUX_DIR(config)では、install-sh等のサポートスクリプトをconfigディレクトリに置くための指定です。IT_PROG_INTLTOOLからAM_GLIB_GNU_GETTEXTまでは、国際化のためです。PKG_PROG_PKG_CONFG, PKG_CHECK_MODULESはpkg-configを使用するための設定です。AC_CONFIG_FILESには、サブディレクトリのMakefileの作成を指示します。

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.68])
AC_INIT([Automake Test], 1.1, foo@example.com, amtest)
AC_CONFIG_AUX_DIR(config)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([src/func.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL

# Checks for libraries.
IT_PROG_INTLTOOL
GETTEXT_PACKAGE=amtest
AC_SUBST(GETTEXT_PACKAGE)
AM_GLIB_GNU_GETTEXT
PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES(GTK, gtk+-3.0)
# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile
                 src/Makefile
   po/Makefile.in])
AC_OUTPUT



AM_GLIB_GNU_GETTEXT等は、これだけでは使用できません。m4フォルダにこれらを使うためのマクロを置く必要があります。以下を実行してください。これでGLIB_GNU_GETTEXTを使用するための準備となります。po/Makefile.in.inも作成されます。必要なm4ファイルが表示されるので、/usr/share/aclocal/フォルダからm4フォルダにコピーしてください(無くても良いようですが)

$ glib-gettextize -c -f

同様にして、pkg.m4, glib-gettext.m4 もコピーしてください。

また各国語用のファイルとして、po/POTFILES.inとpo/LINGUASのファイルを作成します。それぞれ、翻訳する文字列が入っているソースコードと、翻訳する言語を指定します。

po/POTFILES.in
# List of source files which contain translatable strings.
src/func.c

po/LINGUAS
ja

ただ、このままだとpo/LINGUASファイルを参照しないようなので、intltoolも入れます。これで、再度po/Makefile.in.inが書き換わります。MinGWの場合は、このサイトからintltool_0.40.4-1_win32.zipをダウンロードし、解凍したものをmsysのフォルダに上書きしました。ただし、Makefile.in.inのコピーには失敗するので、手動でコピーしています。

$ intltoolize -c -f --automake

ここまで、終われば、後はほぼ従来通りに、以下を行えば良いです。ただし、aclocale実行時には、m4フォルダを指定するようにします。

$ aclocal -I ./m4
$ automake -a -c
$ autoconfig
$ ./cofigure


日本語化に関しては、以下の作業を行います。ja.poで"Hello!"に対応する日本語を設定すれば良いです。

$ cd po
$ make amtest.pot
$ cp amest.pot ja.po
$ vi ja.po


実はMinGW環境で、poフォルダのmake update-poがエラーになる問題が残っているのですが、とりあえずはこれで説明を終わりたいと思います。

世の中には色々なOSがありますが、一つのOSだけで動くソフトウェアを作るよりも多くのOSで動くソフトウェアの方を作るほうが面白いと思っています。その中でC言語を使った場合には、Javaと違いビルドの問題があるのですが、それをautotoolsは解決する仕組みとなっています。

なお、このautotoolsについては以下の書籍を参考にさせていただきました(前回の例はそのまま)。


私の説明は簡単な物でしたが、各コマンドの役割、各ファイルの役割、configure.acでのチェックの指令方法など、更に詳しく書かれています(詳しい分、少しとっつきにくい点もありますが)。他にもLinuxでのプログラム作成に役立つことが色々と書かれており、勉強になりました。Linuxのプログラミングに興味を持つ方は読む価値があるかと思います。

2014年7月27日日曜日

autoconf と automake

autoconf と automake の説明


Linuxでソースコードをダウンロードしてビルドして使用するときには、大抵は同じ手順で行うことが出来ます。tarアーカイブ形式で配布されていて、例えばpkg.tar.gzというtarアーカイブの場合は以下のようになります。

$ tar zxvf pkg.tar.gz
$ cd pkg
$ ./configure
$ make
$ sudo make install

1行目は圧縮ファイルを解凍。
2行目は 解凍したディレクトリへ移動。
3行目は環境の調査とMakefile(ビルドのためのファイル)の作成。
4行目はソースコードのビルド
5行目はインストール

と、非常に簡単にビルドしてインストールができるようになっています。

特徴的なのが3行目でしょう。世の中にはLinux以外にも色々なOSがあります。また同じLinuxであってもPCによってインストールされたソフトウェアが違うなど環境が異なります。configureでそのOSや環境の違いを考慮することができるようになっています。

このconfigureというファイルはスクリプトファイルです。このスクリプトを作成するためのツールがautoconfです。また環境に合わせてビルドを行うためのMakefileをconfigureが書き換えますが、そのMakefileの作成を容易に作成するためのツールがautomakeです。 今回は説明しませんが、ライブラリを作成するためのLibtoolもあります。autoconfとautomakeとLibtoolを合わせてautotoolsと言います。

autoconf と automake の例


これらのautoconfとautomakeを使ったtarアーカイブの作り方の例です。例はAutomake Testという名前のアプリケーションで実行ファイル名はamtest、ファイルはmain.cとfunc.cから作成されています。エディタにはviを使うとします。コマンドの流れは以下のようになります。

$ mkdir amtest; cd amtest
$ vi main.c
$ vi func.c
$ autoscan
$ mv configure.scan configure.ac
$ vi configure.ac
$ vi Makefile.am
$ aclocal
$ autoheader
$ touch NEWS README AUTHORS ChangeLog
$ automake -a -c
$ autoconf
$ ./configure; make
$ make dist


これで、amtest-1.0.tar.gzの作成が出来ます。



1つ目はamtestのディレクトリを作成し、そのディレクトリに移動します。
2つ目はmain.cを作成します。例なので、以下のような簡単なものです。

#include "config.h"

void func();

int main()
{
        func();
        return 0;
}

3つ目はfunc.cを作成します。同じく例で、以下のようなものです。PACKAGE_STRINGは自動的に作成されるconfig.h内に記述されます。

#include "config.h"
#include <stdio.h>

void func()
{
        puts(PACKAGE_STRING);
}

4つ目は、autoscanで自動的にソースコードを検証し、configureの元となるconfigure.scanを作成します。

5つ目ではconfigure.scanをconfigure.acに名前を変更します。autoscanはもしソースコードに大きな修正があった場合には実行し、configure.scanへの差異をconfigure.acに反映することになります。

6つ目ではconfigure.acの中身を書き換えます。変更点は青色の部分です。configure.acの5行目はソフト名、バージョン名、連絡先、パッケージ名の設定で、6行目はautomakeの使用を設定、12行目はインストールスクリプトのチェックの設定、22行目はMakefileを作成することを指定します。

本来なら、ここで他にもヘッダーファイルの存在確認などチェックしたい内容を書き足す必要があります。

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_PREREQ([2.68])
AC_INIT([Automake Test], 1.0, foo@example.com, amtest)
AM_INIT_AUTOMAKE
AC_CONFIG_SRCDIR([func.c])
AC_CONFIG_HEADERS([config.h])

# Checks for programs.
AC_PROG_CC
AC_PROG_INSTALL

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_CONFIG_FILES([Makefile])
AC_OUTPUT

7つ目ではMakefile.inの元となるMakefile.amを作成します。bin_PROGRAMSで実行ファイル名をamtest_SOURCESで全てのソース(ヘッダーを含む)を指定します。

bin_PROGRAMS = amtest
amtest_SOURCES = main.c func.c


8つ目のaclocalでは、autoconfでautomakeを使用するためのマクロであるaclocal.m4を自動作成しています。

9つ目のautohedaderではconfig.hの元となるconfig.h.inを自動作成しています。

10つ目では、automakeで必要となるファイル(NEWS, README, AUTHORS, ChangeLog)をとりあえず空(ゼロバイト)のファイルとして、作成しています。

11個目のautomakeで、Makefile.inが作成されます。-a -c はinstall-sh, missing, depcomp 等のサポートスクリプトを自動作成するためのオプションです。

12個目のautoconfで、configureスクリプトを作成し、これでビルドの準備は完了です。

13個目は正しくプログラムが出来たことを確認するビルド作業です。configureスクリプトを実行すると、Makefile.inとconfig.h.inから、Makefileとconfig.hを作成します。そしてmakeを実行すると実行ファイルのamtestが作成されます。

14個目のmake distで、amtest-1.0.tar.gzが作成されます。ビルドで作成されたMakefile等は含まれず、必要なファイルのみ含まれるようになっています。

tarアーカイブの作成例の説明は以上です。 もちろんFedora 20でもWindows上のMinGWでも同様です。

2014年7月20日日曜日

Chromeの不具合?とWindowsの仕組み

最近、Google ChromeがノートPCのバッテリを過剰消費しているという不具合が話題に上がっていました。

これはFirefoxやInternet Explorerでは起きない不具合となっているのですが、Windowsの仕組みの問題でもあるので、改善されるに越したことはないですが、Chromeを責めるのも少し可哀想な気がします。

OSの仕組み


WindowsなどのOSでは、起動している状態でもかなり多数のアプリケーション(プロセス)が起動しています。それに比べてCPUは多くても8つなど数が少なく、1つの場合もあります。よって、少ないCPUで複数のアプリケーションを切り替えながら実行する必要があります。

OSの中でその切り替えの役割を担っているプログラムをスケジューラと言います。このスケジューラは定期的に実行され、どのアプリケーションがCPUを必要としているかを判断し、実行するアプリケーションをCPUに割り当てます。

このスケジューラを定期的に実行する間隔が、記事に書かれている「システムティックレート」と言われているもので、割り込みコントローラと言うものが、CPUに定期的に実行するように指令しています。

Windowsの場合


Windowsの場合はスケジューラの実行間隔が15msと言うことです。ちなみに最近はあまり見かけないですが、1CPUのパソコンの場合には10msとなっています。

この15msと言う数字は一秒間に約67回スケジューラが動いていると言うことになり、人間から見ると、アプリケーションの切り替えが分からず、同時に複数のアプリケーションが動いているようにみえるわけです。

しかし、15msが速いかというと、コンピュータの世界では非常に遅く(例えば、CPUの1命令が1クロックで行われるとすると、1GHzのCPUでも1命令は0.000001msで完了する)、処理によっては、この間隔で切り替えられても困ることになります。特に動画や音楽関連などリアルタイムな処理をしたい場合は致命的です。

そこで、WindowsではtimeBeginPeriod()というマルチメディア用Win32APIによって「システムティックレート」を切り替えできるようになっています。以下のソースコードのように使い方は非常に簡単です(ビルド時にはwinmm.libをリンクしてください) 。


#include <windows.h>
#include <mmsystem.h>

int WINAPI WinMain(HINSTANCE hInstance,
                HINSTANCE hPrevInstance,
                LPSTR lpCmdLine,
                int nCmdShow)
{
        timeBeginPeriod(1);
        MessageBox(0, "ok end", "sysmtem tick test", MB_OK);
        return 0;
}


ダイアログが表示されますが、ここでOKを押してこのアプリケーションが終了するまで、「システムティックレート」が1msとなります。ちなみにWindows上に動いているアプリケーションの中でtimeBeginPeriod()に指定した値が一番低い値がシステムで使われることになります。

そして、以下のプログラムを上記のプログラムが実行している状態と実行していない状態で動作させてみてください。

#include <windows.h>

int main()
{
        int i;
        for (i = 0; i < 1000; i++) {
                Sleep(1);
        }
        return 0;
}


timeBeginPeriod(1)を実行している状態だと終了が速いはずです。Sleep(1)は少なくとも1msは別のアプリケーション(プロセス)の処理を行ってください、という指令です。しかし「システムティックレート」切り替え間隔が15msの場合には、アプリケーションの切り替えに15msかかってしまうためにSleep(1)は少なくとも15msの時間がかかるためです。

そしてあるCPUでSleep()指令を実行した後に、15ms経って他のアプリケーション切り替えするまでの間はというとCPUはどのアプリケーションも実行していない状態になり、非常に無駄な時間を使うことになります。

そのため頻繁なプロセスの切り替えを行うとパフォーマンスが大幅低下します。Google Chromeは昔から処理が速いことで評判でしたが、 単に消費電力よりパフォーマンスを取った結果と思っていました。

Internet Explorerはバージョン10の時に、消費電力が他のブラウザよりも低いとMicrosoftは宣伝していました。
この頃からChromeはtimeBeginPeriod(1)を使用しているはずですけど、それほどの差はありません。なぜ今ごろまた騒がれているのかが不思議です。もしかすると最近のCPUは省電力機能が発達してきているので、15msの間に積極的に休むようになってきたのかもしれません。

しかし、1つのアプリケーションの timeBeginPeriod(1)の1つの命令でシステム全体に影響を与えるWindowsの仕組みも少々怖い気がします。少なくとも短い間隔のSleep()でタイミングを合わすようなプログラムは書かない方がいいでしょう。

Linuxの場合


Linuxの場合は、tickless(ティックがない)と呼ばれる仕組みが2.6.21から使われています。Windowsのように15msというような一定の期間を設けず、動的に変更するようになっているので、dynaticks(dynamic ticks) (動的なティック)と呼ばれることもあります。

その動的な値を知るためにはPowerTopというソフトで得られます(sudo yum install powertop でインストール)。Fedora 20で確認したところ、色々な普通に使っている状態で一秒間に約800の割り込みが起きています。これを計算すると1.4msぐらいのティックになるので、Linuxは結構消費電力を使っているということになってしまうかもしれません。LinuxではChromeの不具合は起きないと言っても、消費電力を使うのであれば意味がない気がします。ただChrome自体の割り込みは全部合わせても100回/秒程度のようです。

それと、Fedora20ではLinux3.10から導入された完全なticklessであるFull ticklessが使用されていません。確認するには/boot/config-3.13-3-201.fc20.x86_64などのconfigファイルを見ると分かりますが、CONFIG_NO_HZ_FULLが設定されていません。これが使用されると更に消費電力がよくなるかもしれませんが、このティックの辺りはOSの基本の部分であり、少しでも割り込みを減らすのには課題が多そうです。

Mac OS Xも資料が少ないので詳細は判りませんが、ticklessのようです。

2014年7月14日月曜日

Androidでアプリのバックアップ/復元

以前にFedora 19でのNexus 5のroot化のブログを書きましたが、なぜroot化をするかを書いていませんでした。

Androidをroot化する理由は、Titanium Backupというアプリを使うためと言っても良いかもしれません。

Androidは、端末をリセットした際などに再度同じGoogleアカウントを設定すると、以前の同じユーザーのアプリを入れなおして、ある程度前と同じ環境に復元してくれます。

しかし、設定した内容や保存した内容が忘れられてしまうアプリがほとんどです。この点だけでもなんとかなってくれるとAndroidも更によくなると思うのですけど・・・。

そこで、 Titanium Backupというアプリを利用すると、設定した内容や保存した内容のバックアップと復元が可能になります。

Titanium Backupには有料版と無料版がありますが、無料版はアプリを一つずつしかバックアップできず、不便な点があります。バックアップするアプリが多い人は有料版を使用した方がいいでしょう。

無料版の使い方は簡単です。「バックアップ/復元」を選択するとアプリのリストが出てきます。 


ここでバックアップや復元をしたいアプリを選択すると、以下のような表示がでるので、
バックアップまたは復元を行ってください。


PCに接続すると内部ストレージ直下にTitaniumBackupというフォルダがありますので、OSの入れ替えを行うなどリセットする際にはPCにバックアップをとっておき、復元するときには、元の位置に戻せば良いです。

2014年6月28日土曜日

Nexus 5 に Android Lを入れてみた

Google I/O 2014において、Androidの次バージョン(バージョンは5.0?)、Android Lが発表されました。せっかくNexus 5を持っているので、Android Lを入れてみました。

入れ方を簡単に説明すると以下のようになります。Fedoraではrootで実行する必要があること、それとNexus 5のデータは全て消えることになりますので、注意してください。
  • AndroidのSDKを入れる。(Fedora20の場合のインストール方法)
  • fastbootモードで起動する。((ボリュームダウンと電源キーを両方押しながら起動する)
  • PCと接続してブートアンロックをする。(コマンドラインで fastboot oem unlockを実行)
  • Android Lのイメージをここからダウンロードして解凍する。
  • 解凍して出来たファイルのflash-all.bat(Windowsの場合)、flash-all.sh(Mac OS, Linuxの場合)を実行する。
上記を行うと、Android Lがインストールされ、再起動します。再起動は結構時間がかかる(測っていないが10分以上?)ので気長に待ちましょう。




Android Lの特徴は以下です。
  • ARTという仕組みで2倍近い高速化
  • 「Material Design」という新しいデザイン
  • 省電力化
それで、使ってみた感想はというと、デザイン面ではここを見るとすごい気がしますが、あまり変わったような感じはしませんでした。アプリのMaterial Design対応がされていないからかもしれません。

高速化に関しても、それほど実感はできませんでした。省電力に関しても色々とさわっているせいでしょうが、それほど感じませんでした。

まだ開発者版プレビューですし、あまり期待しない方がいいのかもしれません。

一応OSの機能である通知、ロック画面、アプリ一覧などは変わっています。通知は2段階引き伸ばしで操作はしやすくなったのですが、バッテリー残量の確認や、通知の一括削除がしにくくなったなど、良し悪しがある感じです。

アプリの方は確認したかぎりではFirefoxが異常終了してしまいます。Dolphin BrowserもJet PackをONにすると異常終了してしまいます。もしかるすとFlash Playerが関連しており、ついにFlash Playerがダメになってしまったのかもしれません。また、QuickPicも異常に速度が遅いです。他にも色々とあるかもしれません。

開発者の人やよほど新しいもの好き以外の人は、急いで入れないほうがいいでしょう。

2014年6月14日土曜日

64bit化の利点 その2

前に64bit化の利点と欠点を書きましたが、その2です。

64bit化の利点として、他にも以下があるとのことでした。
  1. 消費電力が良い
  2. セキュリティが良い
1の消費電力が良いについては、ソースは忘れましたが、64bitの方がコードが最適化されているためだとかなんとかだったような・・・ ドライバの出来にも左右されるようです。Linuxでは、ドライバもソースは同じと思われますので、計測してみました。

 使用したのは以下のTAP-TST5です。


使用したOSはFedora20のLiveUSBで、Core i5-2500Kのデスクトップ機に以下のUSBメモリに入れてOSを起動しました。

OSが起動後、しばらくしてから確認すると32bit版と64bit版の両方とも41ワットでした。

元のソースが分からないですが、ARMの64bit版の話で、Intelの話では無かったかもしれません。また、64bitの方が速度が早く、処理が速く終わるからという話であったかもしれません。

ただし、メモリ使用量はやはり、64bitの方が多いです。freeコマンドで確認したところ、以下のようになっていました。

OSアイドル時消費電力使用メモリ(キャッシュ含む)使用メモリ(キャッシュ除く)
32bit41W1,368,672MB402,600MB
64bit41W1,594,216MB621,040MB

もしかすると、細かくは違うのかもしれませんが、少なくてもデスクトップ機レベルでのアイドル時においては消費電力はあまり変わらないようです。






セキュリティについては、ゴールデンウィークにInternet Explorerの脆弱性が話題になりましたが、64bitであれば対策方法があるとの話がありました。これは、64bit版のWindowsでは、DEP(データ実行防止:データに埋め込まれたプログラムの実行を阻止する)と呼ばれるセキュリティ機能がデフォルトで有効になっているためです。32bit版では設定を行う必要があります。

また、ASLR(アドレス空間配置のランダム化:データやプログラムのメモリに配置される位置をランダム化することで、読み取りや書き換えを困難にする)においても64bitの方がメモリの範囲が広いためによりランダムにしやすく、攻撃しにくいそうです。

特にWindowsを使う人はセキュリティを考えるなら64bit版を使用した方がいいのでしょう。

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命令が指定できたり更に複雑な処理ができるようです。興味のある方は試してみてください。

2014年4月28日月曜日

C/C++ 処理の流れ その4 並列処理

並列処理は同時に処理を行うことで、長い処理の間にユーザーが別の処理を行えるようにするなど、使い勝手の面で重要でした。

さらに最近では、CPUも単体での性能は頭打ちになってきており、CPUを複数使って、並列に処理することで高速化することも重要となってきました。

基本的にC/C++言語での処理の流れは、単体での処理となっており、並列処理はOS依存であったりライブラリであったりと、同じような処理を行うにも方法が様々です。

最近の新しいバージョンのC/C++では、標準で並列処理をライブラリで持っていますが、普及はまだまだのような気がします。

並列処理にはプロセスとスレッドとコルーチンがありますが、一番一般的なスレッドを扱う方法について説明します。

Win32でのスレッド(C言語)


Windowsの場合は<process.h>の_beginthredex()関数を使用してスレッドを作成し、並列処理を行います。CreateThread()というWin32APIもあるのですが、こちらよりbeginthreadex()関数の方が良いです(標準ライブラリの初期化をため、標準ライブラリを使用する場合は必須です)。

#include <stdio.h>
#include <process.h>
#include <windows.h>

unsigned int __stdcall print_hello(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("Hello\n");
                Sleep(10);
        }
        return 0;
}

unsigned int __stdcall print_world(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("World\n");
                Sleep(10);
        }
        return 0;
}

int main()
{
        HANDLE th;

        th = (HANDLE)_beginthreadex(NULL, 0, &print_hello, NULL, 0, NULL);
        print_world(NULL);
        WaitForSingleObject(th, INFINITE);        
        return 0;
}

上記では、分かりやすくするためにスレッドの作成に失敗した場合等のエラー処理は全く行っていないので注意してください。

_beginthraedexで、新しいスレッド(並列処理)を作成して、その並列処理でprint_hello()関数を実行します。元の処理はそのまま次に進みprint_world()関数を実行します。そのため、print_hello()関数とprint_world()関数は同時に行われます。

最後にWaitForSingleObject()関数で、並列処理のスレッドの終了を待って、プログラムを終了します。 これを行わないと、終わる前にプロセスが終了してしまい、スレッドの処理は中断されることになります。

その他、本来であれば、printf()の処理は同じ端末に対して行うので、本来であれば排他処理が必要なのかもしれません。排他処理は同じデータに並列処理が同時にアクセスし、データが異常になるのを防ぐために行います。

詳細は、Microsoftの開発者用のサイトを参照してください。


Linux(POSIX)でのスレッド(C言語)



LinuxやMac OS X等のUNIX系ではPOSIXという規格があり、その中でpthreadライブラリが定義されています。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void *print_hello(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("Hello\n");
                usleep(10);
        }
        return NULL;
}

void *print_world(void *dmy)
{
        int i;

        for(i = 0; i < 20; i++) {
                printf("World\n");
                usleep(10);
        }
        return NULL;
}

int main()
{
        pthread_t th;

        pthread_create(&th, NULL, &print_hello, NULL);
        print_world(NULL);
        pthread_join(th, NULL);        
        return 0;
}

コンパイルをするときには -lpthreadのオプションを追加する必要があります。関数名や引数がWindowsとは違いますが、やっていることは同じなのでほとんど同じ記述になります。

またMinGW環境でも利用可能です。


C++11でのスレッド


C++の2011年番のC++11では<thread>にstd::threadが追加されました。以下がprint_hello()関数とprint_world()関数を並列動作させる例です。残念ながらMinGW環境ではコンパイル時にエラーとなります。

#include <stdio.h>
#include <thread>

void print_hello()
{
        for(int i = 0; i < 20; i++) {
                printf("Hello/n");
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
}

void print_world()
{
        for(int i = 0; i < 20; i++) {
                printf("World/n");
                std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
}

int main()
{
        std::thread th(print_hello);
        print_world();
        th.join();
        return 0;
}


C11でのスレッド


C言語でも2011年版のC11において、<threads.h>にスレッドを扱うthrd_t型や、thrd_create()関数、thrd_jon()関数が追加されています。残念ながらgccでもサポートがまだのようです(Fedora20, Mingwでthraeds.hが無いことを確認)。


その他のライブラリ



並列プログラミングは、どちらかというと歴史が新しく色々なライブラリがあります。例えば以下です。C++11のstd::threadの元となったライブラリでC++98でも使用できます。
  • boost::thread

 std::threadなどの標準のライブラリは、低レベル(人間に分かりにくく、コンピュータ寄りになっているが、制御は細かくできる)になっていますが、より分かりやすくするためにも色々なライブラリがあります。これらではコンピュータにコアがいくつ付いているかを自動的に判定し、それぞれのコアに処理を分けて振るようになっています。
  • OpenMP
  • MPI(Message Passing Interface)
  • TBB(Intel Threading Building Blocks)
  • Intel Click Plus
  • PPL(Microsoft Parallel Pattern Library)



また、今は同じ種類のCPUを使うホモジニアスマルチコアでなく、違う種類のCPUを使用するヘテロジニアスマルチコアという種類のマルチコアがあります。違う種類のCPUとしてよく使われるのがグラフィックを描画するコアであるGPU(Graphics Processing Unit)です。

現在のGPUは3D画像を扱うのが当たり前になっており、元々大量の3次元処理が得意と言うこともあり、画像処理だけでなく一般の計算処理にも使用しようとしたものです。GPGPU(General-purpose computing on graphics processing units)と言われます。

そのようなヘテロジニアスを使うライブラリとしては以下があります。
  • OpenCL
  • CUDA
  • OpenACC
  • C++AMP(C++ Accelerated Massive Parallelism)

今の所、ヘテロジニアスではメモリ空間が分離されており、スレッドというよりプロセス的な使い方が必要となっていますが、AMDが推進しているHMA(Heterogeneous System Architecture)のhUMAではメモリも統合されており、使い勝手も向上するのでは無いかと思います。


他にも一応、複数の演算を並行して行うという点では、SIMD演算と呼ばれるCPUの以下の命令も並列処理と言えるかもしれません。一つの命令で複数のデータの演算を行うことができます。
  • MMX, SSE, AVX(x86/x64)
  • NEON(ARMv7)
  • AltiVec, VMX(Power)
  • VIS(SPARC)
  • MIPS-3D, MDMX(MIPS)

CPUごとに命令が違うのですが、これらをC++上から同様に扱うMicrosoftのDirectXMachやlibsimdppなどのライブラリもあるようです。

上記のように並列処理には色々な実装があります。いつかは標準でもっと簡単に使えるようになって欲しいですが、現状ではOS標準のAPIの使用が一番いいかもしれません。


2014年4月13日日曜日

C/C++ 処理の流れ その3 繰り返し

コンピュータが一番得意とする処理として、繰り返し(ループ)処理があります。同じ作業を何度も繰り返すことは人の作業でもよくあることですが、コンピュータなら、速く失敗なく何度も繰り返すことができます。

for文


繰り返し処理は、条件分岐とジャンプの組み合わせで記述することもできますが、上述の様に重要かつよく使われる機能なので、特別な記述方法が用意されています。一番よく使用されるのが、for文で、以下のような文法になります。

    for (初期値設定式 ; 繰り返し条件式 ; 繰り返し付加式)
        処理



初期値設定式は、繰り返しの前に実行される式です。繰り返し条件式の値が真(0以外)である間、処理を繰り返します。処理の最後には繰り返し付加式が実行されます。繰り返し付加式では基本的には繰り返し条件に関する更新内容のみ記述します。

例えば、以下のように掛けば、10回"Hello World¥n"を表示することになります。

        int i;

        for (i = 0; i < 10; i++) {
                printf("Hello World¥n");
        }

iという短い変数名が使用されていますが、これはiterator(イテレータ:反復子)のiの意味があり、非常によく使われます。続きでj, kと使われることも多いです。

まずi = 0;が実行されます。そしてi < 10が評価され、評価値は真であるため、printf()が実行され、その後i++が行われます。そしてi < 10の評価に戻り繰り返します。

10回繰り返すには、(i = 1; i <=10; i++)の方が分かりやすいと言う方もいると思いますが、上記のように0で初め、終わりの値は10を含めない書き方が普通です。配列の操作などにおいて、コンピュータではそれが便利だからです。

処理においては、break文とcontinue文を書くことが可能です。break文は、繰り返しの処理をそこで終了し、for文の次の文にジャンプします。continue文は繰り返しの処理をそこで終了し、繰り返し付加式にジャンプします。

break文もcontinue文もジャンプというgoto文と同じ性質から構造化プログラミングの観点でふさわしくないとルールで禁止される場合があります。しかし個人的には禁止は反対です。無理に禁止することで返ってネストが深くなりプログラムが見にくくなったり、処理がわかりにくなったりすることも多いと思います。

少し条件分岐に比べて複雑なので、フローチャートで書くと以下のような処理に流れとなります。


while文


for文の初期値設定式繰り返し付加式を取り除いた文法です。既に初期値が設定されており、記述が必要ない場合で、よく使われます。

    while (繰り返し条件式)
        処理


do while文


for文やwhile文においては、繰り返し条件式が繰り返しを行う前に必ず評価されます。その評価が必要なく、処理を必ず一回は行いたい場合に使用します。あまり使うことはないです。

    do
        処理
    while (繰り返し条件式);


無限ループ


初期値設定式繰り返し条件式繰り返し付加式は記述をしなくても良いです。繰り返し条件式は記述しなかった場合は1と等価であり、常に真になるので、無限に繰り返し処理を行うことになります。

繰り返し条件式が複雑でbreak文で終了したり、 他の並列処理から終了したりする場合に使われることがあります。while文でもfor文でも良いですが、for文を使用して、for( ; ; )と書くのが一般的のようです。


配列のループ(C言語)


ループを一番よく使用するケースとして、複数のデータの集合である「配列」の処理を行うことが多いです。配列についてはまだ説明をしていないのですが、色々な種類があります。まずはC言語の場合です。

#include <stdio.h>

int main()
{
        int arr[] = {1, 12, 123};
        int i;

        for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
                printf("%d¥n", arr[i]);
        };
        return 0;
}

sizeof()は演算子でデータのバイト数を取得します。配列のサイズを取得するのに、配列全体のサイズから配列一つのサイズを除算して求めています。環境によってはマクロが用意されている場合があります(LinuxカーネルソースではARRAY_SIZE()マクロ、Windowsでは_ARRAYSIZE()マクロなど)。

配列のループ(C++98)


C++では、ライブラリで定義されたstd::vector等の配列のデータ型があります。これらのデータ型の場合は、メンバ関数としてbegin()関数とend()関数が用意されているため、それらを使用して、開始と終了の値を取得します。取得されたデータ型は〜::iterator型となります(他にもconst_iteratorやreverse_iterator等の型もあります)。iterator型の値はポインタ(これもいつか説明する)の様に*を先頭につけることで配列の値となります。

#include <stdio.h>
#include <vector>

int main()
{
        std::vector<int> arr;

        arr.push_back(1);
        arr.push_back(12);
        arr.push_back(123);

        for (std::vector<int>::iterator i = arr.begin(); i < arr.end(); i++) {
                printf("%d¥n", *i);
        }
        return 0;
}

その他にもC++では配列のループ処理を助ける関数として<algolithm>にfor_each()関数が定義されたりしています。


配列のループ(C++11)


C++にもバージョンが色々とありますが、1998年に出来たC++98に比べて、2011年に出来たC++11では配列の操作関数が色々と強化されています。

例として、以下のようになります。

#include <stdio.h>
#include <iterator>
#include <vector>

int main()
{
        int arr[] = {1, 12, 123};
        std::vector<int> arr2 = {2, 23, 234};
        int i;

        for (auto i = std::begin(arr); i < std::end(arr); i++) {
                printf("%d¥n", *i);
        }
        for (auto i = std::begin(arr2); i < std::end(arr2); i++) {
                printf("%d¥n", *i);
        }
        for (auto i : arr2) {
                printf("%d¥n", i);
        }
        return 0;
}


まず、メンバ関数のbegin()やend()ではなく、通常の関数のstd::begin()やstd::end()関数が<iterator>に用意され、その使用が奨励されています。一番目のループと2番目のループのようにstd::vectorなどの定義型だけでなく、通常のint型にも使えるのが利点です。

autoは直接ループとは関係無いですが、型推論と呼ばれるもので、型を自動的に判別する仕組みです。std::vector<int>::iteratorなどと長い記述をする必要がなくなりましたので、かなり楽になります。

また、std::vector等において、{1, 2, 3}の記述で値を代入する初期化リストにも対応し、int型等の通常の配列と同様に初期化ができるようになっています。

そして、新しい構文としてbegin()やend()を使用せずに配列内全ての処理を行う以下の構文が追加されました。

    for( : 配列)
        処理


再帰


関数から同じ関数を呼び出すことで、繰り返しを行うこともできます。これを再帰と言います。以前に階乗の処理を紹介しました。このときは、for文を使っていましたが、以下のようにfor文を使わず、再帰で表現することも可能です。

下記が再帰を使った例です。do_fact1()関数もdo_fact2()関数も階乗を求める関数ですが、do_fact1()関数はfor文を使用したもので、do_fact2()関数が再帰を使ったものです。

#include <stdio.h>
#include <stdlib.h>

int do_fact1(int value)
{
        int fact, i;

        fact = 1;
        for (i = 1; i <= value; i++) {
                fact = fact * i;
        }
        return fact;
}

int do_fact2(int value)
{
        return value <= 0 ? 1 : value * do_fact2(value - 1);
}

int main(int argc, char *argv[])
{
        int value;

        if (argc != 2) {
                printf("argument error¥n");
                return 1;
        }

        value = atoi(argv[1]);

        if (value &lt 0) {
                printf("value error¥n");
                return 2;
        }

        printf("factorial1 = %d¥n", do_fact1(value));
        printf("factorial2 = %d¥n", do_fact2(value));

        return 0;
}


do_fact2()では階乗の計算が

fact(n) = n * fact(n - 1)
fact(0) = 1

という式で表されることから、do_fact2(value)関数の中で引数を変えて、do_fact2(value -1)関数を呼んでいます。これが再帰です。

再帰を使った繰り返しの利点とては以下があります。
  • 言語としてforの機能が無くても使用できる
  • ハノイの塔やクイックソートなど、再帰でないと記述が難しいアルゴリズムを書ける
一つ目ですが、C++ではテンプレートという機能を使って静的(コンパイル時)に階乗の計算を行うことができますが、このテンプレートにはforの機能がないです。そのため再帰を利用して以下のように書きます。

#include <stdio.h>

template<int N>
class Fact {
public:
        enum { value = N * Fact::value };
};

template<>
class Fact<0> {
public:
        enum { value = 1 };
};

int main()
{
        printf("%d\n", Fact<5>::value);
}

欠点としては以下があります。
  • メモリの使用量が多く、使用量の推定が難しい
関数の呼出は以前に説明したように、スタックというメモリ領域を使用します。そのため、再帰呼出しが多いとそのメモリ領域を多く使うことになります。メモリの少ない場合やどの程度の関数呼び出しがあるか不明な組み込み系などの場合には再帰を使わないほうがいいでしょう。


2014年4月5日土曜日

C/C++ 処理の流れ その2 条件分岐

ユーザーの入力や、様々な要因によって処理の流れを変えたい場合があります。その場合には条件分岐を使用します。

if文


一番主に使われる条件分岐はif文で使用文法は以下です。まずは一番基本的な文法です。条件式が正しい場合(真,trueの場合)、処理1を行い、条件式が違う場合(偽,falseの場合)に処理2を行います。


    if (条件式)
        処理1
    else
        処理2

基本的な文法からelseを省いた形です。条件式の正しい場合に処理1を行います。

    if (条件式)
        処理1

この形はよく使うのですが、安易に使用すると問題になることが多いです。elseの偽の場合の処理が本当に不要か考えたほうが良いです。

条件式の書き方


C/C++言語では0が偽で、0以外が真と見なされます。条件式は比較演算以外でも、どんな演算式でも許されます(例えばif (1)のように数値のみでも大丈夫です)。そのため、以下の2つは間違えやすいので注意が必要です。

    if (a = 10) {
        :
    }

    if (a == 10) {
        :
    }

上側の"="はaに10を代入する演算です。代入演算の結果は代入された値のため演算結果は10です。そのため、このif文の処理は必ず真で実行されます。

下側の"=="はaと10を比較する演算です。比較演算の結果はaの値が10であれば1であり、10でなければ0となります。そのため、このif文の処理はaの値が10であれば真の処理を実行し、10でなければ偽(else)の処理を実行します。

通常は下側の比較演算子を使いますが、間違えないようにするため、以下のように右辺と左辺を逆にする記述を薦める場合もあります(代入演算の場合はエラーになる)。

    if (10 == a) {
        :
    }

しかし、最近のコンパイラでは警告が表示されますし、感覚的にはわかりにくいと思うため、私はお薦めしません。

また条件式には関数を入れることもできますが、&&演算など演算子によっては、関数が実行されないこともあるので、注意が必要です。

処理の書き方


処理の書き方としては分岐後の処理は段を下げて書くのが一般的ですが、段を下げることで処理が変わるわけではありません。それとif分の後に書ける処理は1つのみです。複数の処理を行いたい場合は{}で処理を囲みます。

色々な書き方がありますが、以下のA, B, C, D, E, F, Gの処理を見てください。

    /* A */
    if (a == 10) {
        printf("Hello");
    }
    printf("World");

    /* B */
    if (a == 10)
    {
        printf("Hello");
    }
    printf("World");

    /* C */
    if (a == 10) ;
    { 
        printf("Hello");
    }    
    printf("World");

    /* D */
    if (a == 10) {
        printf("Hello");
    printf("World");
    }

    /* E */
    if (a == 10) 
        printf("Hello");
    printf("World");

    /* F */
    if (a == 10) 
        printf("Hello");
        printf("World");

    /* G */
    if (a == 10) printf("Hello");
    printf("World");

この中で、CとDのみ動作が違い、後のA, B, E, F, Gは同じ動作をします。

Cは条件式の後ろにセミコロン(;)が付いているので、そこで条件が真の時の処理の記述が終了してしまい、"Hello"と"World"は常に表示されます。

Dは条件式が真の時だけ"Hello"と"World"がされます。

その他は条件式が真のときに"Hello"が表示され、"World"は常に表示されます。

Eの書き方は、処理の見やすさとしては問題ないのですが、Fの書き方の問題を起こしやすく、Appleのgotofail問題など、大きな問題となったことがあります。そのため、Eの書き方よりもAのように1つの処理でも{}を使用するAの書き方が奨励されます。

Bの書き方はAの書き方より空白が多く見やすいですが、Cの間違いが起きることがあるため、Aの方が良いという意見があります。

Gの書き方は横に伸びすぎたり、処理部が見にくくなる問題があるかもしれません。

複数の条件式と複雑度


プログラムが大きくなってくると、分岐処理がどんどん多くなっていき、条件分岐の中でさらに条件分岐という形になっています。

    if (条件式1) {
       :
        if (条件式2){
  :
            if (条件式3) {

このようになってくると、プログラムが見にくくなり、複雑度がまし、不具合が多くなることになります。条件分岐は不具合が最も多く出る所と言ってもよいでしょう。

そのために条件分岐を少しでも減らす工夫が必要です。

まず、書き方の工夫ですが、条件により処理を切り替える場合のif文の書き方としては、以下のような書き方があります。

    if (条件式1)
        処理1
    else if (条件式2)
        処理2
    else if (条件式3)
        処理3
         :
         :
    else
    どの条件にも当てはまらない場合の処理

この書き方であれば、ネストが深くなることもありません。

switch文


前述のような else ifのような複数の処理の分岐において、条件式が単純な変数と整数値の一致比較であれば、switch文を使うことができます。switch文の文法は以下です。

    switch (整数の変数){
    case 整数値1:
        処理1
        break;
    case 整数値2:
        処理2
        break;
    case 整数値3:
        処理3
        braek;
         :
         :
    default:
        どの整数値も一致しなかった場合の処理
        break;
    }

整数の変数と整数値1が一致したときに、その処理1が行われます。 break;という行でbreak文がありますが、これはswich文を抜けることを意味します。break文がない場合は、次のcase文の処理も行われます。例えば以下のように、複数の値に一致した場合の処理が同じ場合にbreak文を書かない書き方が使用できます。

    switch (整数の変数){
    case 整数値1:
    case 整数値2:
    case 整数値3:
        処理1,2,3
        braek;
         :
         :
    default:
        どの整数値も一致しなかった場合の処理
        break;
    }

しかし、break文を記述し忘れて問題になることもありますので、注意が必要です。

またdefault:は無くてもエラーにはなりません。しかし、if文においてelseを考えないと問題を起こすことがあるのと同様に、条件に合わなかった部分を考えることは重要ですので、defaultは必ず書くようにした方が良いです。

言語によっては、整数値以外がswitch文で使用できますが、C/C++言語では整数値のみです。


その他の条件分岐 三項演算子を利用する


if文はC/C++言語では文となっていますが、他のプログラミング言語(特に関数型言語と言われる言語)では、式となっていて、条件分岐後の値を返す場合もあります。

C/C++言語でもそのような使い方はできて、三項演算子という演算子があり、以下のような文法です。

    条件式 ? 値1 : 値2

条件式が真の場合には値1を返し、条件式が偽の場合には値2を返します。例えば、以下の2つは同じ意味になります。

    if (a == 0) {
        b = 10;
    } else {
        b = -10;
    }

    b = (a == 0 ? 10 : -10);

if文のように条件式を必ず()で括る必要がないので、注意が必要です。三項演算子全体を()で括っていますが、これは見やすさのためです。

たまに三項演算子の使用をルールで禁止するとこもあるようですが、式であることから色々な使い方ができ、場合によってはプログラムをより簡潔に見やすくすることができます。例えば、以下のような書き方もできます。

    printf(a == 0 ? "Hello" : "World"); 

aが0の場合に"Hello"を表示し、0でない場合に"World"を表示します。

その他の条件分岐 論理演算子(使用しないほうがいい)


論理演算子を使って、条件分岐を行うことも可能です。Perlと言う言語で使われているのを見たことがありますが、C/C++言語では見たことがないです。使わない方がいいでしょう。

    a == 0 || printf("Hello");
    a == 0 && printf("World"); 

上の文は、a == 0が偽のときだけ、||の後のprintfの処理が実行されます。下の文はa == 0が真のときだけ、&&の後のprintfの処理が実行されます。論理演算子の動作を利用したものです。

使うつもりは無くても、条件式の中で使ってしまうこともあるので注意してください。


その他の条件分岐 配列を利用する


配列とはデータの集合のことです。例えば、0〜6の数値を得て、その数値に対する曜日の文字列を返す関数get_week()を作成するとします。

その場合、以下のようにif文を使って書くことができます(さぼって、水曜日までにしています)。

const char *get_week(int num)
{
        if (num == 0) {
                return "Monday";
        } else if (num == 1) {
                return "Tuesday";
        } else if (num == 2) {
                return "Wednesday";
        } else {
                return "";
        }
}

配列を使うと、以下の様に書くこともできます。

const char *get_week(int num)
{
        static const char week[][10]= {"Monday", "Tuesday", "Wednesday" };

        if (num < 0 || num > 2) {
                return "";
        }

        return week[num];
}

この配列を使うということには色々な利点があります。
  1. コード量を少なく、見やすくすることができる。
  2. 関数の配列にすれば処理も自在に変更することができる。
  3. 配列の中身を変更することで、分岐処理自体を変更することができる。 
常に配列が良いとは限りませんが、配列を利用できることは案外と多く、条件分岐を使用せずに、配列で代用できないかを考えることができるようになれると良いのではないかと思います。

その他の条件分岐 多態性を利用する


多態性とは、オブジェクト指向言語などにおいて、プログラミングの型に持たせる性質のことです。ポリモーフィズムや、ポルモルフィズムなど色々と呼ばれています。型によって異なった動作しますので、条件分岐の代わりとして使うことができます。

あまり良い例ではありませんが、以下がC++での多態性の例です。

#include <stdio.h>
#include <stdlib.h>

class Iclass {
public:
        virtual void print() const = 0;
};

class Aclass : public Iclass
{
public:
        void print() const {
                printf("Hello");
        }
        int test;
};

class Bclass : public Iclass
{
public:
        void print() const {
                printf("World");
        }
        int test;
};

void time3(const Iclass &ic)
{
        ic.print();
        ic.print();
        ic.print();
}

int main(int argc, char *argv[])
{
        if (argc != 2) {
                return 1;
        }

        if (atoi(argv[1]) == 0) {
                time3(Aclass());
        } else {
                time3(Bclass());
        }
        return 0;
}


time3()関数で、3回print()の関数を呼び出していますが、このとき引数の型がAclassかBclassかで、time3()関数で表示される文字列が"Hello"か"World"かが変わります。

time3()関数内はif文が使われていないことに着目してください。つまり3回print()を実行していますが、3回のif文が必要無いのです。このように分岐を使わず、型によって処理を分けることで、if文の量を減らすことができます。

上記はC++言語ですが、C言語でも関数ポインタなどを利用すれば多態性と同じことをすることができます。

オブジェクト指向言語では、よいプログラムの書き方をパターン化したデザインパターンという設計手法がありますが、これらでは多態性や配列を使用することによってif文があまり無いようになっています。


2014年3月30日日曜日

C/C++ 処理の流れ その1

コンピュータでは色々な計算をして、適切なタイミングで計算結果を表示したり、音にしたりでI/Oに出力します。

その処理の流れには主に以下があります。
  1. 逐次実行
  2. ジャンプ
  3. サブルーチン
  4. 条件分岐
  5. 繰り返し
  6. 並列実行

逐次実行


逐次実行は一番基本的な処理の流れで、順番に処理をこなしていくだけです。C/C++言語も最終的にはCPUにおいて機械語で命令が実行されますが、音楽の手拍子のように決まったタイミングで命令が実行されていきます。この手拍子の速度がクロック周波数と言われるもので、基本的にはクロック周波数が速いということになり、パソコンの速度を示す指標に使われます(実際には1回の手拍子で2つの命令を実行することができたり、クロック周波数だけで速度は決まりません)。

C/C++言語では、基本的な実行は逐次実行となります。演算において、

    a = b + c + d;

とあれば、b と c の加算を行い、その結果と d の加算を行い、その結果をaに代入すると順番に実行されていきます(演算の順番については演算の優先順位と言うものが存在し、その順で実行されていきます)。

また、以下のように

    処理1;
    処理2;
    処理3;

とセミコロンで処理を分けた場合にも順に処理1、処理2、処理3と順番に実行されていきます。

ただし、処理が関数の場合、関数の中で並列実行がされている可能性があり、その場合は処理2が完全に終了する前に処理3が行われるケースもあります。最近のWindows8での Windows Store アプリ用の関数(WinRT API)は並列実行が基本となっているので注意が必要です。

ジャンプ


ジャンプは、処理を別の場所に飛ばすことになります。通常は後述の条件分岐と一緒に使うことになります。

C/C++言語では

 goto ラベル

とすることで、ラベルの位置にジャンプします。例えば以下のプログラムではlabel5というのがラベル名です。コロン(:)を付けることでラベル名であることを示します。

#include <stdio.h>

int main()
{
        printf("1\n");
        printf("2\n");
        goto label5;

        printf("3\n");
        printf("4\n");

        label5:
        printf("5\n");
        return 0;
}

このプログラムでは、1,2と表示したところで、label5にジャンプするので、3,4は表示されずに5が表示されます。

関数をまたぐ場合にジャンプを使用するには、setjmp.hを使用して、setjmp()関数、longjmp()関数を使用します。

C/C++言語では、構造化プログラミングと言って、処理を階層的に表し、入口と出口をはっきりさせる記述方法が奨励されており、その立場ではジャンプはあまり好ましくない手法とされています。しかし、使い方によってはよりプログラムを分かりやすくする場合もあり、一概に悪いとは言い切れないです。初心者はまずは使わない方が良いでしょう。

サブルーチン


プログラムを見やすくするために処理を一つにまとめたものをサブルーチンと言います。プログラミング言語によっては、単に処理をまとめた「サブルーチン」と、返り値を必要とする「関数」を別に扱う言語もありますが、C/C++言語では「サブルーチン」と「関数」は同じ扱いになっています。下記がその例で、func1とfunc2のサブルーチン(関数)を使っています。

#include <stdio.h>

void func1(void);

void func2(void)
{
        printf("func2 1\n");
        printf("func2 2\n");
        return;
}

int main()
{
        printf("main\n");
        func1();
        func2();
        printf("main end\n");
        return 0;
}

void func1(void)
{
        printf("func1 1\n");
        printf("func1 2\n");
        return;
}


main()関数からfunc1()関数とfunc2()関数に処理が飛び、それらの関数からreturnでmain()関数に戻ってきます。C/C++言語ではジャンプより、このようなサブルーチンを使って処理を移動することが奨励されています。

なお、func1, func2の前にあるvoidが返り値が無いことを示しています。return文でも返り値を書いていません(このように返り値が無い場合にはreturn文を省略しても良いです)。

また、main()関数で、func1()関数が使用されていますが、func1()関数はmain()の後ろに書かれています。C/C++言語は上からプログラムが解釈されていくため、main()関数で突然func1()関数が出てきても理解できません。そのため、3行目でfunc1()という関数がどこかで書かれていることを示しています。

注意点としては、サブルーチンは機械語的になったときには、スタックというメモリ領域に返り値が覚えられるため、サブルーチンを入れ子にして使いすぎるとメモリをたくさん使用してしまう問題があります。


return文でのジャンプ


上述のように関数(サブルーチン)を終了するときには、return文を使用します。return文は処理の最後である必要はないですし、数も一つの関数に一つというわけではなく、いくつ書いても構いません。

しかし、構造化プログラミングの、一つの入口に一つの出口の観点からは関数においてreturn文は最後に一つにすべきとのルールもがある場合があります。実際にそのように作った方がリソースリークという問題は起きにくいです。

ただ、このルールもやり過ぎるとネストが深くなったり、かえってプログラムが見にくくなる場合もあります。

個人的には引数のエラーチェック等を関数の初めの方にに行い、エラーを返すような場合には、途中のreturn文を書いていいと思っています。そして、何より関数の行数を長くしすぎないことが重要です。

条件分岐処理や繰り返し処理はまた次回。