Pythonによる並列プログラミング -GPGPUも-

pythonでの並列プログラミングの勉強した結果を載せてみます。
pycudaというCUDAのpythonバインディングも試してみました。


single_thread_execute.py

import numpy
import time

size = 256
arr_0 = numpy.arange(size*size)
arr_1 = numpy.arange(size*size)
arr_result = numpy.zeros(size*size)

print "execute start"
start = time.time()

count = 0
for i in range(size):
	for j in range(size):
		tmp = 0
		for k in range(size):
			row = k + (i*size)
			col = j + (k*size)
			tmp += arr_0[row] * arr_1[col]
		arr_result[j + (i*size)] = tmp
	#print "i = " + str(i)

print "time = " + str((time.time() - start))
#print arr_result.sum()

result_sum = 0
for i in arr_result:
	result_sum += i	
print result_sum 

multi_thread_execute.py

import numpy
import threading
import time

size = 256
arr_0 = numpy.arange(size*size)
arr_1 = numpy.arange(size*size)
arr_result = numpy.zeros(size*size)

task_complete = 0
class task(threading.Thread):
    def __init__(self, i):
	threading.Thread.__init__(self)
	self.i = i

    def run(self):
	global task_complete
	global size
	global arr_0
	global arr_1
	global arr_result

	i = self.i
	for j in range(size):
		tmp = 0
		for k in range(size):
			row = k + (i*size)
			col = j + (k*size)
			tmp += arr_0[row] * arr_1[col]
		arr_result[j + (i*size)] = tmp
	task_complete += 1
	#print "i = " + str(i)


print "execute start"
start = time.time()
count = 0

thread_arr = []
print "task init"
for i in range(size):
	thread_arr.append(task(i))

print "start tasks"
for i in range(len(thread_arr)):
	thread_arr[i].start()

while True:
	if(task_complete  >= size):
		break

print "done"
print "time = " + str((time.time() - start))
#print arr_result.sum()

result_sum = 0
for i in arr_result:
	result_sum += i	
print result_sum 

multi_process_execute.py

import numpy
import threading
import time
from multiprocessing import Process, Value, Array, Lock, Queue



size = 256
arr_0 = numpy.arange(size*size)
arr_1 = numpy.arange(size*size)


def task(q, shared_arr_result):
	print "start process "
	while True:	
		if q.empty():
			break

		i = q.get()	
		for j in range(size):
			tmp = 0
			for k in range(size):
				row = k + (i*size)
				col = j + (k*size)
				tmp += arr_0[row] * arr_1[col]
			shared_arr_result[j + (i*size)] = tmp
		#print  str(i)


if __name__ == '__main__':
	size = 256
	q = Queue()
	for i in range(256):
		q.put(i)
	print "execute start"

	arr_result = numpy.zeros(size*size)
	shared_arr_result = Array("f", arr_result)

	process_arr = [0] * 4
	print "task init & start"
	start = time.time()
	for i in range(len(process_arr)):
		process_arr[i] = Process(target=task, args=(q, shared_arr_result))
		process_arr[i].start()


	while True:
		if q.empty():
			break

	print "time = " + str((time.time() - start))
	result_sum = 0
	for i in shared_arr_result:
		result_sum += i	
	print result_sum 


pycuda_execute.py

import pycuda.autoinit
import pycuda.driver as drv
import numpy
import time
from pycuda.compiler import SourceModule

mod = SourceModule("""
	__global__ void square(int* arr_0, int* arr_1, int* arr_result)
	{
          const int size = 256;
          const int x = blockIdx.x * blockDim.x + threadIdx.x;
          const int y = blockIdx.y * blockDim.y + threadIdx.y;

          int tmp = 0;
          for(int k = 0; k < size; k++) {
              int row = k + y * size;
              int col = x + k * size;
              tmp += arr_0[row] * arr_1[col];
          }
          arr_result[x + y * size] = tmp;
	}
""")

size = 128
block = 4

arr_0 = numpy.arange(size*size)
arr_1 = numpy.arange(size*size)
arr_result = numpy.zeros(size*size)


print "start"
print arr_result

start = time.time()
square = mod.get_function("square")
square(drv.InOut(arr_0), drv.InOut(arr_1), drv.InOut(arr_result), block=(block, block, 1), grid=(size/block, size/block))

print "end... time = " + str((time.time() - start))
print arr_result

tomcat7とsolr3.5.0のセットアップ(日本語の文字化け対策も)

最新版のsolrとtomcatを準備
apache-tomcat-7.0.23
apache-solr-3.5.0


tomcat7のインストールパスを$TOMCAT
solrのインデックスファイルや設定ファイルを格納するパスを$SOLR_HOMEとする

  • solrのWEBアプリケーションを$TOMCAT/webappに配備
$ cp apache-solr-3.5.0/example/webapps/solr.war $TOMCAT/webapps/
  • デフォルトの設定ファイルやインデックスファイルなどをコピー
cp -r apache-solr-3.5.0/example/solr/* $SOLR_HOME/
※apache-solr-3.5.0/exampleはexampleというよりはスケルトンに近い
  • ライブラリをコピー
cp -r apache-solr-3.5.0/contrib $SOLR_HOME/
cp -r apache-solr-3.5.0/dist $SOLR_HOME/
  • インデックスなどが格納されるデータディレクトリの設定を変更する
vim $SOLR_HOME/solrconfig.xml

${solr.data.dir:}${solr.data.dir:$SOLR_HOME/data}  
  • ライブラリのパスを変更する(相対パスになっているがどこが起点になってるのかよくわからん)
vim $SOLR_HOME/solrconfig.xml

  
  
  
  
  
  
  
  
  
  
  
  
  
  
  
JAVA_OPTS="$JAVA_OPTS -Dsolr.home=$SOLR_HOME"    

tomcat起動するとhttp://localhost:8080/solr/admin/で管理画面が見えていることを確認

  • schema.xmlを設定してみる
<?xml version="1.0" encoding="UTF-8" ?>
<schema name="example" version="1.4">

 <types>
  <fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/> 

  <fieldType name="text_cjk" class="solr.TextField" positionIncrementGap="100" >
      <analyzer>
	    <tokenizer class="solr.CJKTokenizerFactory"/>
      </analyzer>
  </fieldType>
 </types>


 <fields>
     <field name="id" type="string" indexed="true" stored="true" required="true" />   
     <field name="name" type="text_cjk" indexed="true" stored="true" required="true" />   
     <field name="original_name" type="text_cjk" indexed="true" stored="true" required="true" />   
     <field name="kana" type="text_cjk" indexed="true" stored="true" required="true" />   
     <field name="name_en" type="text_cjk" indexed="true" stored="true" required="true" />   
 </fields>

 <uniqueKey>id</uniqueKey>

 <!-- field for the QueryParser to use when an explicit fieldname is absent -->
 <defaultSearchField>name</defaultSearchField>

 <!-- SolrQueryParser configuration: defaultOperator="AND|OR" -->
 <solrQueryParser defaultOperator="OR"/>

</schema>
  • autocommitをonにする

solr_home/conf/solrconfig.xmlの下記コメントアウトを外す

        
         10000
         1000 
       
  • このままでは日本語が文字化けして検索にヒットしない問題

tomcatのexampleアプリケーションにSetCharacterEncodingFilter.classがあるのでそれを流用すると方法がネットで出てきた
http://blog.sedays.com/0409/2219_15.php
が、tomcat7にはなぜかSetCharacterEncodingFilter.classがない、、、、

    $ ls -la apache-tomcat-7.0.23/webapps/examples/WEB-INF/classes/filters/
    ExampleFilter.class
    ExampleFilter.java

仕方ないのでtomcat6をダウンロードしてきてSetCharacterEncodingFilter.classを手に入れる

mkdir -p $TOMCAT/webapps/solr/WEB-INF/classes/filters
cp apache-tomcat-6.0.35/webapps/examples/WEB-INF/classes/filters/SetCharacterEncodingFilter.class .

solrアプリケーションにfilterを設定する
$ vim $TOMCAT/webapps/solr/WEB-INF/web.xml

    <filter>
      <filter-name>Set Character Encoding</filter-name>
      <filter-class>filters.SetCharacterEncodingFilter</filter-class>
      <init-param>
	<param-name>encoding</param-name>
	<param-value>UTF-8</param-value>
      </init-param>
    </filter>
    <filter-mapping>
      <filter-name>Set Character Encoding</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

http://kuromame.dip.jp/xoops/modules/pukiwiki/?solr
によるとSolrRequestFilterより前に記載しないとだめらしい、、、(未確認)

server.xmlを修正

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
	       redirectPort="8443" 
	       useBodyEncodingForURI="true"/> ←追記
  • データの流し込みはあえてjythonで行った(jython使ってみたかっただけ)

SolrJへのクラスパスを通した上でjythonを起動して以下のように実行

	from org.apache.solr.client.solrj.impl import CommonsHttpSolrServer;
	from org.apache.solr.common import SolrInputDocument

	server = CommonsHttpSolrServer("http://localhost:8080/solr")
	doc = SolrInputDocument()
	doc.addField("id", dic['manageId'])
	doc.addField("name", dic['artistName'])
	doc.addField("original_name", dic['artistNameOriginal'])
	doc.addField("kana", (dic['artistNameKANA'] if (dic.has_key('artistNameKANA')) else ""))
	doc.addField("name_en", (dic['artistNameEN'] if (dic.has_key('artistNameEN')) else ""))
	server.add(doc)

Lion + Titanium Mobile1.8.5 + iosSDK5.0での実機転送

開発端末が今まで使っていたMacBookAir(Snow Leopard)からMacBookPro(Lion)にパワーアップしたのでアプリ開発環境も構築し直しなった。

before
MacOS 10.6 Snow Leopard + Titanium Mobile 1.7.3 + XCode4.2(iOS SDK4.3)

after
MacOS 10.7 Lion + Titanium Mobile 1.7.5 + XCode4.2(iOS SDK5.0)



開発しているTitanium Mobileアプリケーションは自作のモジュールをいくつか利用しているので念のため全部再ビルド。
ビルドしたモジュールをアプリケーションに配置してシミュレータで起動したところ問題なく起動しました。


しかし、Titanium Studioから実機に転送しようとすると下記エラーが発生。

[ERROR] Error: Traceback (most recent call last):
File "/Library/Application Support/Titanium/mobilesdk/osx/1.6.2/iphone/builder.py", line 1091, in main
execute_xcode("iphonesimulator%s" % link_version,["GCC_PREPROCESSOR_DEFINITIONS=__LOG__ID__=%s DEPLOYTYPE=development TI_DEVELOPMENT=1 DEBUG=1 TI_VERSION=%s" % (log_id,sdk_version)],False)
File "/Library/Application Support/Titanium/mobilesdk/osx/1.6.2/iphone/builder.py", line 1012, in execute_xcode
output = run.run(args,False,False,o)
File "/Library/Application Support/Titanium/mobilesdk/osx/1.6.2/iphone/run.py", line 39, in run
sys.exit(rc)
SystemExit: 65

ビルドするpythonコードを追ってxcodebuildのコマンドを試しに手動で実行するとarmv6がビルドできない的なエラーが出てました。
とりあえず以前の環境のMacBookAirで再度ビルドをためしてみると
MBA + Ti SDK1.7.3 + ios SDK4.3 OK
MBA + Ti SDK1.7.5 + ios SDK4.3 OK
MBP + Ti SDK1.7.5 + ios SDK5.0 NG
の切り分けになった。


どうやらios sdk5.0が怪しい。


iOS SDK4.3を試したいがためにMacBookProのほうでxcode4.2をアンインストールして、xcode3.2をインストールしてから、xcode4.2を上書きインストールしてみるも状況変わらず。


泣きそうになりながらネットを調べていると状況的には同じ質問があるが、最新のTitaniumMobileではiOS5.0対応していると記載がある。。。
http://developer.appcelerator.com/question/120631/ios-5

さらに泣きそうになりながらネットを調べているとどうやらxcode4.2にアップグレードすると色々設定が変わってしまう的な記事を発見。
http://ishidak.blogspot.com/2011/10/xcode-42-upgrade-problem-solution.html


書いてある通りにarmv6を設定

これを全モジュールに設定してビルドしなおしてからアプリケーションに組み込めば問題なく動きました。



ちなみにMacを乗り換えたタイミングでユーザーが管理者ユーザーではなくなったせいなのかTitanium SDKのインストール先が/Library/Application\ Support/Titaniumから~/Library/Application\ Support/Titaniumになっていたので、各モジュールのTITANIUM_SDKのパスを~/Library/Application\ Support/Titaniumに変更してあります。

この問題のせいで2営業日を無駄にした、、、

自宅サーバのcuda環境を3.2→4.0にバージョンアップ

基本的にはドキュメントに書いてある通りセットアップすればOK
http://developer.nvidia.com/cuda-toolkit-40


自宅サーバのOSはCentOS 5.6。
私は雰囲気でインストールしてたらこんなエラーが出るようになってしまった。

$ ./deviceQuery
[deviceQuery] starting...
./deviceQuery Starting...

 CUDA Device Query (Runtime API) version (CUDART static linking)

cudaGetDeviceCount returned 38
-> no CUDA-capable device is detected
[deviceQuery] test results...
FAILED

Press ENTER to exit...

ドキュメントのpdfを見たらGUIを使っていない場合は/dev/nvidia*のデバイスファイルを手動で確保しないといけないらしくドキュメントに記載されているシェルを実行する必要があったみたいだ。

#!/bin/bash
/sbin/modprobe nvidia
if [ "$?" -eq 0 ]; then
# Count the number of NVIDIA controllers found.
NVDEVS=`lspci | grep -i NVIDIA`
N3D=`echo "$NVDEVS" | grep "3D controller" | wc -l`
NVGA=`echo "$NVDEVS" | grep "VGA compatible controller" | wc -l`
N=`expr $N3D + $NVGA - 1`
for i in `seq 0 $N`; do
mknod -m 666 /dev/nvidia$i c 195 $i
done
mknod -m 666 /dev/nvidiactl c 195 255
else
exit 1
fi

これでdeviceQuery実行したらちゃんと起動できました。
にして、これ電源入れ直すたびに実行しないといけないっぽい、、、、
最近は節電のために自宅サーバは日中は電源切っているので、忘れないようにしないと、、、

MindTouch DekiWikiをセットアップした

今までその手軽さからPukiWikiを使っていましたが、PukiWikiは残念ながら「履歴管理」「ユーザー登録(誰が編集したか)」「ページへのタグ付け」ができません。
そこでMoonGiftさんでべた褒めで紹介されていたMindTouch Deki Wikiを使ってみることにしました。



MindTouch Deki Wikiのサイトを見る限り、非常に高機能でさらに嬉しいのがドキュメントページには各種OSごとのインストール方法が詳細に書かれています。
http://developer.mindtouch.com/en/docs/mindtouch_setup/010Installation


会社の人がUbuntsにセットアップしてみたところあっというまにセットアップ完了していましたが、私のチームではCentOS5.4で利用することにしました。(特に理由はありません)
事前にネット検索するとCentOS5でセットアップに苦しんでいる人が結構いたので、すんなりはいかなそうな予感、、、


基本的には公式サイトのやり方ににそって作業を行っていけばOKのはず。
私は同居しているredmineの関係でapacheのバージョンとapache上でrailsアプリを走らせるpassengerのバージョン、さらにはPHPのバージョンでつまずきまくりましたが、、、、


なんとか構築はできましたが以下2点メモとして残しておきます。


・サブディレクトリでの稼動はできないようです。
私が構築した環境ではredmineも同じサーバで動いているので、
www.hoge.co.jp/redmine
www.hoge.co.jp/wiki
みたいな感じで同一apache上で動かしたかったのですが、Aliasを使ってサブディレクトリで動かすみたいな方法はできないみたいです。
フォーラムの議論によるとサブディレクトリでのdekiwiki稼動はできないとのこと。



こちらでは無理してできないことはないだろうけど、今後のバグFIXなどのアップデートができなくなるからお勧めしないとのこと。
おとなしくVirtualHostか別ポートで動かしましょう。



・ブラウザからの必要事項入力後のインストールでエラーになる
インストール後にブラウザでアクセスして必要事項(管理者情報など)を入力した後にinstallをクリックするとエラーが出る。
mysqlのsuperuserという入力箇所ににrootを入力してパスワードは空欄にすると画面に

Error (Code: 2) mysql_select_db() expects parameter 2 to be resource, boolean given

と出ます。
詳細なところ(表示がちっちゃい)には

#0 [internal function]: DekiError::handleError(2, 'mysql_select_db...', '/var/www/dekiwi...', 1058, Array)
#1 /var/www/dekiwiki/includes/Database.php(1058): mysql_select_db('wikidb', false)
#2 /var/www/dekiwiki/maintenance/install-utils.inc(600): Database->selectDB('wikidb')
#3 /var/www/dekiwiki/config/includes/deki_installer.php(541): install_database(Object(ConfigData))
#4 /var/www/dekiwiki/config/install.php(53): DekiInstaller->installMindTouch(Array)
#5 [internal function]: InstallController->start()
#6 /var/www/dekiwiki/deki/cp/includes/deki_mvc.php(157): call_user_func_array(Array, Array)
#7 /var/www/dekiwiki/deki/cp/includes/deki_mvc.php(88): DekiController->executeAction('start', Array)
#8 /var/www/dekiwiki/config/includes/deki_installer.php(62): DekiController->initialize()
#9 /var/www/dekiwiki/deki/cp/includes/deki_mvc.php(60): DekiInstaller->initialize()
#10 /var/www/dekiwiki/config/install.php(130): DekiController->__construct(Array)
#11 {main}
Error (Code: 2) mysql_select_db() expects parameter 2 to be resource, boolean given

がでます。
mysql側を見るとdekiwiki用のデータベースはcreateされているのでその後の初期化で失敗している様子。


該当まわりのソースを見てみたところ、#1の mysql_select_db('wikidb', false)のところで第二引数にコネクションオブジェクトを渡さないといけないのが、コネクション取得に失敗して変数にコネクションオブジェクトではなくfalseが入ってしまっているのが原因ぽい。

/var/www/dekiwiki/includes/Database.php:228あたりでコネクションを取得していたので
$server
$user
$password
デバッグプリントを仕込んでもう一度実行してみたところ、画面からはmysql superuserの箇所にいろいろ入力してみたが$server以外の変数に何も表示されてこない、、、、


もう面倒なのでとりあえず
/var/www/dekiwiki/includes/Database.php:228行目あたりを

@/**/$this->mConn = mysql_pconnect( $server, $user, $password );

@/**/$this->mConn = mysql_pconnect( $server, "root", "" );

ってベタで書いた実行したらうまくいった。


最初のインストール時にだけ必要なテーブルやmysqlユーザーを自動生成するためにmysqlの管理者権限ユーザーが必要なだけみたいなので、インストール完了したらコードも元に戻せば大丈夫だよね、、、、


知らんけど。

Titanium MobileでPNG最適化をスキップする

iPhone開発をしているときにxcodeでビルドすると、アプリケーションにパッケージされるpng画象が独自フォーマットに変換される処理が入るらしい。
https://discussions.apple.com/thread/1751896?start=0&tstart=0


ここで紹介されている方法でxcodeに設定するとこのpng変換処理がスキップされるみたいです。
http://d.hatena.ne.jp/Seasons/20100330/1269922563



しかしTitanium Mobileで同じようにテンプレートプロジェクト(/Library/Application\ Support/Titanium/mobilesdk/osx/1.7.1/iphone/iphone/Titanium.xcodeproj)をxcodeで開いて同じ設定をしてもどうしてもoptimize pngが走ってる。。。


Titanium SDKのソースを見たところxcodeでビルド時ではなく、コマンドラインで直接最適化処理をいるのを発見しました。
(/Library/Application Support/Titanium/mobilesdk/osx/1.7.1/iphone/compiler.py:314あたりの処理)

どうやらiPhoneSDKにそういうコマンドが用意されているみたいですね。

cd /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/
./iphoneos-optimize
usage: iphoneos-optimize path [-skip-PNGs]


とりあえずcompiler.pyのiphoneos-optimizeコマンド実行の処理を

run.run(["%s/Platforms/iPhoneOS.platform/Developer/usr/bin/iphoneos-optimize"%dev_path,app_dir, "-skip-PNGs"],False)

として-skip-PNGsパラメータを付けてあげたらpng変換処理はスキップされました。

Titanium MobileでTwitterのOAuth認証でプロフィール画象アイコンの変更する

TwitterのOAuth認証はAuthorizationヘッダ方式がおすすめらしいとのこと。
http://dev.twitter.com/pages/auth


とりあえず今回はTitanium Mobileで開発を行っているためappceleratorブログで紹介されていたOAuthライブラリを利用してみることに。


しかしながら、このoauth-adapterライブラリは二つ問題があります。

  • OAuth認証が(postbody内での)パラメータ渡しになっている
  • OAuthAdapter.sendメソッドに渡したAPIパラメータが全てoauth_signature生成時の署名対象になってしまう。


前者に関してはそもそもTwitterのOAuth認証はAuthorizationヘッダ方式が推奨されていることからあまり芳しくはないけど、まぁほとんどのAPIではパラメータ渡しでも動くのですぐには問題にならない。
※ちなみにjavaのTwitterAPIライブラリであるTwitter4Jはソース見たらちゃんとAuthorizationヘッダでPOSTしていました。


しかし、update_profile_imageやupdate_profile_background_imageのプロフィール画象変更するAPIに関しては、この二つの問題から動作しないのです。


update_profile_imageやupdate_profile_background_imageは「OAuth による本API実行時の oauth_signature 生成に際しては “image 引数”は署名の対象には含まない」とのことなので、oauth_adapter.jsのsendメソッドで普通にパラメータで渡してしまうと署名対象に入ってしまってOAuth認証を通すことができません。


なので、twitterのプロフィール画象を変更するupdate_profile_imageやupdate_profile_background_imageなどのmultipartでPOSTするようなAPIは対応できません。
※OAuthの場合、multipartで送るときはAuthorizationヘッダ方式で送ってやらないとだめみたいです。


oauth-adapterのOAuth認証まわりをAuthorizationヘッダ方式に全部書き直したいところだけどそこまでやるのは面倒なので、、、
とりあえずoauth_adapter.jsのsendメソッドの下あたりにAuthorizationヘッダ方式+multipartでファイル送信するメソッドを追加してみました。

    var sendMultiPart = function(pUrl, realm, pParameters, pTitle, pSuccessMessage, pErrorMessage)
    {
        Ti.API.debug('Sending a message to the service at [' + pUrl + '] with the following params: ' + JSON.stringify(pParameters));

        if (accessToken == null || accessTokenSecret == null)
        {

            Ti.API.debug('The send status cannot be processed as the client doesn\'t have an access token. The status update will be sent as soon as the client has an access token.');

            actionsQueue.push({
                url: pUrl,
                parameters: pParameters,
                title: pTitle,
                successMessage: pSuccessMessage,
                errorMessage: pErrorMessage
            });
            return;
        }

        accessor.tokenSecret = accessTokenSecret;

        var message = createMessage(pUrl);
        message.parameters.push(['oauth_token', accessToken]);
        OAuth.setTimestampAndNonce(message);
        OAuth.SignatureMethod.sign(message, accessor);

        parameterMap = {};
        for(p in pParameters) {
                parameterMap[pParameters[0][0]] = pParameters[0][1];
        }
		
        var client = Ti.Network.createHTTPClient();
        client.open('POST', pUrl, false);
        client.setRequestHeader("Authorization", OAuth.getAuthorizationHeader(realm, message.parameters) );
        client.send(parameterMap);

        if (client.status == 200) {
            Ti.UI.createAlertDialog({
                title: pTitle,
                message: pSuccessMessage
            }).show();
        } else {
            Ti.UI.createAlertDialog({
                title: pTitle,
                message: pErrorMessage
            }).show();
        }

        Ti.API.debug('*** sendStatus, Response: [' + client.status + '] ' + client.responseText);

        return client.responseText;

    };
    this.sendMultiPart = sendMultiPart;


呼び出し方はAPI呼び出しの部分を

        var file = Ti.Filesystem.getFile("hoge_image.jpg");
	oAuthAdapter.sendMultiPart('http://api.twitter.com/1/account/update_profile_image.json', //←sendメソッドではなくsendMultiPartに変更
						"hogehoge",  //←Authorizationヘッダで指定するrealmを入れる
						[['image', file.read()]], 
						'Twitter', 
						'Published.', 
						'Not published.');

な感じで書いてあげればちゃんと画象変更できるようになりました。


ここらへんが超参考になった
The OAuth 1.0 Protocol
Twitter API 仕様書 日本語訳 第五十版
OAuth(Twitter API)
JavaScript で OAuth 認証を行う方法