tag:blogger.com,1999:blog-3825247397294456492024-03-22T00:15:51.115+09:00SGソフトウェア開発ブログ株式会社エス・ジーの社員が日々のソフトウェア開発で学んだ技術を公開していくブログです。sgblog60http://www.blogger.com/profile/12455664571932809532noreply@blogger.comBlogger216125tag:blogger.com,1999:blog-382524739729445649.post-17631904456251542982021-03-08T12:00:00.037+09:002021-03-08T12:00:00.466+09:00[JavaScript] DockerでJavaScriptコマンドライン環境導入JavaScriptの実行環境としては、ブラウザ上で行うのがお手軽だけど、プログラミング言語としてのJavaScriptを試したい時は、Node.jsでコマンドライン実行できる環境があると便利。<br>
<br>
<h2><span style="color:gray">Node.js on Alpine on Doocker</span></h2>
とはいえ、ローカル環境に入れたくない時もあるだろうから、Dockerのコンテナとして入れてしまおう。<br>
軽量のAlpineを使う。<br>
homeをホスト側に置いて、JavaScriptのファイルはそこに置くことにしよう。<br>
<a name='more'></a>
<br>
Dockerfile
<pre class="prettyprint linenums">
FROM alpine:latest
RUN apk add --update --no-cache nodejs
</pre>
docker-compose.yml
<pre class="prettyprint linenums">
version: "3"
services:
node-app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./home:/home
tty: true
</pre>
<h2><span style="color:gray">起動</span></h2>
homeディレクトリを作って起動しよう。
<pre class="prompt">
$ mkdir home
$ docker-compose up -d
</pre>
<h2><span style="color:gray">ログイン</span></h2>
起動できたので、ログインするけどその前にhomeに確認用プログラムを置いておく。
<pre class="prompt">
$ cat home/sketch.js
console.log('Hello, node on docker!');
</pre>
そして、起動。
<pre class="prompt">
$ docker-compose exec node-app ash
/ # cd home/
/home # ls
sketch.js
/home # node sketch.js
Hello, node on docker!
/home #
</pre>
<h2><span style="color:gray">停止</span></h2>
使い終わったら停止しておこう。
<pre class="prompt">
$ docker-compose stop
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-81042405000686565862020-12-14T12:00:00.119+09:002020-12-14T12:00:02.138+09:00[JavaScript] Chart.jsで確率分布のシミュレーション表示 その2<a href="https://blog.sgnet.co.jp/2020/12/javascript-chartjs.html">その1</a>では、確率分布をシミュレーションするための発生頻度を求める関数を作成した。<br>
今回は二項分布を生成して、Chart.jsでグラフに表示してみよう。
<a name='more'></a>
<h2><span style="color:gray">二項分布</span></h2>
前回作成したベルヌーイ分布関数を呼び出して、二項分布の乱数発生関数を作ろう。
<pre class="prettyprint linenums">
// 確率pで1を、確率1-pで0を返す
function bernoulli(p) {
return Math.random() <= p ? 1 : 0;
}
// 確率pで発生した事象の回数を返す
function binomial(p, n) {
let m = 0;
for (i = 0; i < n; i++) {
m += bernoulli(p);
}
return m;
}
</pre>
<h2><span style="color:gray">Chart.jsの散布図のためにx,y連想配列に変換</span></h2>
連想配列のままでは、Chart.jsの散布図では表示してくれないので、xとyをキーとするオブジェクトの配列に変換するユーティリティ関数を作成しておこう。
<pre class="prettyprint linenums">
function xy_list(list) {
let freq = [];
for (const key of Object.keys(list)) {
freq.push({ x:key, y:list[key] });
}
return freq;
}
</pre>
<h2><span style="color:gray">Chart.jsを使って散布図を表示</span></h2>
Chart.jsを使って、確率0.5と0.2の二項分布を表示すると以下のようになる。
<canvas id="binomialGraph"></canvas>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.js"></script>
<script type="text/javascript">
function bernoulli(p) {
return Math.random() <= p ? 1 : 0;
}
function binomial(p, n) {
let m = 0;
for (i = 0; i < n; i++) {
m += bernoulli(p);
}
return m;
}
function frequency(list) {
let freq = [];
for (const key of list) {
if (freq[key] === undefined) freq[key] = 0;
freq[key]++;
}
return freq;
}
function gen_random(func, n) {
return (new Array(n)).fill().map((_, i) => func());
}
function freq10K(func) {
return frequency(gen_random(func, 10000));
}
function xy_list(list) {
let freq = [];
for (const key of Object.keys(list)) {
freq.push({ x:key, y:list[key] });
}
return freq;
}
// 二項分布グラフ
let binomialGraph = document.getElementById("binomialGraph");
let bin_05_10 = xy_list(freq10K(() => binomial(0.5, 10)));
let bin_02_10 = xy_list(freq10K(() => binomial(0.2, 10)));
let binomialChart = new Chart(binomialGraph, {
type: 'scatter',
data: {
datasets: [
{
label: 'bin(0.5, 10)',
data: bin_05_10,
backgroundColor: 'RGBA(225,0,0, 1)',
},
{
label: 'bin(0.2, 10)',
data: bin_02_10,
backgroundColor: 'RGBA(0,225,0, 1)',
}]
},
options:{
title: {
display: true,
text: '二項分布'
},
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: '事象発生回数'
},
ticks: {
suggestedMin: 0,
suggestedMax: 10,
stepSize: 1,
callback: function(value, index, values){
return value
}
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: '頻度'
},
ticks: {
suggestedMax: 4000,
suggestedMin: 0,
stepSize: 500,
callback: function(value, index, values){
return value
}
}
}]
}
}
});
</script>
<h2><span style="color:gray">今回の散布図表示スクリプト</span></h2>
最後に、今回の散布図表示スクリプトを掲載。
<pre class="prettyprint linenums">
<canvas id="binomialGraph"></canvas>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.bundle.js"></script>
<script type="text/javascript">
function bernoulli(p) {
return Math.random() <= p ? 1 : 0;
}
function binomial(p, n) {
let m = 0;
for (i = 0; i < n; i++) {
m += bernoulli(p);
}
return m;
}
function frequency(list) {
let freq = [];
for (const key of list) {
if (freq[key] === undefined) freq[key] = 0;
freq[key]++;
}
return freq;
}
function gen_random(func, n) {
return (new Array(n)).fill().map((_, i) => func());
}
function freq10K(func) {
return frequency(gen_random(func, 10000));
}
function xy_list(list) {
let freq = [];
for (const key of Object.keys(list)) {
freq.push({ x:key, y:list[key] });
}
return freq;
}
// 二項分布グラフ
let binomialGraph = document.getElementById("binomialGraph");
let bin_05_10 = xy_list(freq10K(() => binomial(0.5, 10)));
let bin_02_10 = xy_list(freq10K(() => binomial(0.2, 10)));
let binomialChart = new Chart(binomialGraph, {
type: 'scatter',
data: {
datasets: [
{
label: 'bin(0.5, 10)',
data: bin_05_10,
backgroundColor: 'RGBA(225,0,0, 1)',
},
{
label: 'bin(0.2, 10)',
data: bin_02_10,
backgroundColor: 'RGBA(0,225,0, 1)',
}]
},
options:{
title: {
display: true,
text: '二項分布'
},
scales: {
xAxes: [{
scaleLabel: {
display: true,
labelString: '事象発生回数'
},
ticks: {
suggestedMin: 0,
suggestedMax: 10,
stepSize: 1,
callback: function(value, index, values){
return value
}
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: '頻度'
},
ticks: {
suggestedMax: 4000,
suggestedMin: 0,
stepSize: 500,
callback: function(value, index, values){
return value
}
}
}]
}
}
});
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-36857221050314652452020-12-07T12:00:00.001+09:002020-12-07T12:00:09.765+09:00[JavaScript] Chart.jsで確率分布のシミュレーション表示 その1正規分布のイメージとして、二項分布の試行回数をどんどん多くしていった時の近似というのがあるけど、実際にそういう状況をシミュレートしたら案外上手くいったので、調子に乗って他の分布もやってみた。<br>
<br>
確率密度関数を使うのではなく、乱数を使って分布を生成しているので、リロードするたびに微妙に違う形になるのが面白い。<br>
<a name='more'></a>
<h2><span style="color:gray">準備</span></h2>
まずは準備として、ベルヌーイ分布の乱数を生成する仕組みを作ってみよう。<br>
<br>
引数で与えられた確率で、1か0を返す関数
<pre class="prettyprint linenums">
function bernoulli(p) {
return Math.random() <= p ? 1 : 0;
}
</pre>
<br>
乱数の分布を表現するために、引数で与えられた配列の値をキーとして、登場する回数を要素とする連想配列を生成しよう。<br>
<pre class="prettyprint linenums">
// 配列の要素の頻度(登場回数)を連想配列で返す
function frequency(list) {
let freq = [];
for (const key of list) {
if (freq[key] === undefined) freq[key] = 0;
freq[key]++;
}
return freq;
}
// funcで与えられた関数をn回呼び出して、結果を配列にして返す
function gen_random(func, n) {
return (new Array(n)).fill().map((_, i) => func());
}
// funcを10000回呼び出して、発生頻度を配列で返す
function freq10K(func) {
return frequency(gen_random(func, 10000));
}
</pre>
<br>
これにより、以下のように発生頻度を求めることができる。
<pre class="prettyprint linenums">
let rand_ber = gen_random(() => bernoulli(0.5), 5);
// rand_ber [ 1, 1, 0, 1, 0]
let freq_ber = frequency(rand_ber);
// freq_ber { 1 : 3, 0 : 2 }
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-43851126161785171842020-10-19T12:00:00.004+09:002020-10-19T12:00:06.541+09:00[bash] xargsでシェル関数を並列処理<h2><span style="color:gray">シェル関数を並列実行したい</span></h2>
シェル関数を並列実行したい時にxargsで関数をそのまま呼んでもうまくいかない。<br>
<br>
parallel.sh
<pre class="prettyprint linenums">
#!/bin/sh
function func()
{
sleep 1
echo $1
}
echo $1 | tr -s ',' '\n' | head -n6 | xargs -n1 -P6 -I% func %
</pre>
<pre class="prompt">
$ ./parallel.sh 1,2,3,4,5,6,7,8,9
xargs: func: No such file or directory
</pre>
<br>
xargs自身はシェルではないので、シェル関数は認識できない。<br>
つまり、xargsには外部コマンドを渡す必要がある。<br>
<br>
なので、次に考えるのはシェルにコマンド文字列を渡して起動するやり方<br>
<br>
parallel.sh
<pre class="prettyprint linenums">
#!/bin/sh
function func()
{
sleep 1
echo $1
}
echo $1 | tr -s ',' '\n' | head -n6 | xargs -n1 -P6 -I% sh -c 'func %'
</pre>
<pre class="prompt">
$ ./parallel.sh 1,2,3,4,5,6,7,8,9
sh: func: command not found
sh: func: command not found
sh: func: command not found
sh: func: command not found
sh: func: command not found
sh: func: command not found
</pre>
<br>
残念ながら、これもうまくいかない。起動したシェル側に外側のシェルで定義した関数がうまく渡っていないからだ。<br>
こういう時は -f オプション付きで関数を export する必要がある。<br>
<br>
parallel.sh
<pre class="prettyprint linenums">
#!/bin/sh
function func()
{
sleep 1
echo $1
}
export -f func
echo $1 | tr -s ',' '\n' | head -n6 | xargs -n1 -P6 -I% sh -c 'func %'
</pre>
<pre class="prompt">
$ ./parallel.sh 1,2,3,4,5,6,7,8,9
3
1
4
2
5
6
</pre>
<br>
これで、ようやく1秒後に一斉に数字が表示されるようになった。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-5178715995627519072020-10-05T12:00:00.030+09:002020-10-05T12:00:05.687+09:00[Java] なぜ、DBコネクションをマルチスレッドで共有してはいけないのかマルチスレッドでDBにアクセスする場合、各スレッドごとにコネクションプールからコネクションを取得する形式が多い。<br>
DBコネクションはマルチスレッドで共有しない方がいいと言われるけど、なぜ共有してはいけないんだろうか。<br>
<br>
いくつかのDBMSの説明を見てみよう。<br>
<a name='more'></a>
<h2><span style="color:gray">Oracle</span></h2>
Oracle Database JDBC Developer's Guide<br>
JDBC and Multithreading<br>
<a href="https://docs.oracle.com/database/121/JJDBC/apxtips.htm#JJDBC28949">https://docs.oracle.com/database/121/JJDBC/apxtips.htm#JJDBC28949</a><br>
<br>
これを見る限りでは、Oracleではコネクションはマルチスレッドをサポートしているけど、スレッドごとにコネクションを持つことを推奨している。<br>
その理由としては、スレッドごとに処理がシリアライズされることによるパフォーマンス低下としている。<br>
<br>
<h2><span style="color:gray">PostgreSQL</span></h2>
The PostgreSQL JDBC Interface<br>
Chapter 10. Using the Driver in a Multithreaded or a Servlet Environment<br>
<a href="https://jdbc.postgresql.org/documentation/head/thread.html">https://jdbc.postgresql.org/documentation/head/thread.html</a><br>
<br>
PostgreSQLでは、Oracleと違い、JDBCドライバはスレッドセーフではないと書かれている。<br>
これでは、1つのコネクションをマルチスレッドで同時に使うと、パフォーマンス以前に処理がおかしくなってしまうだろう。<br>
<br>
<h2><span style="color:gray">結論</span></h2>
そもそも、JDBCドライバがスレッドセーフでないDBMSもあるけど、仮にJDBCドライバがスレッドセーフだとしても、SQL関連の処理がシリアライズされてしまうので、結局マルチスレッド処理にしている旨味がない。<br>
という訳で、DBコネクションはマルチスレッドで共有しない方がいいということになるんだね。
Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-77915990055392867192020-09-28T12:00:00.060+09:002020-09-28T12:00:04.697+09:00[Oracle] 文字列と数値の暗黙の型変換SQLでは型が暗黙に変換されてしまうことがある。その中でも文字列と数値はやってしまいがちだけど、理解していないと機能的にもパフォーマンス的にも問題が発生する。<br>
<br>
具体的にどっちがどっちに変換されるのか見てみよう。<br>
検証はOracle 18c<br>
<br>
<a name='more'></a>
<h2><span style="color:gray">varhar2とnumberをカラムに持つテーブルを作成</span></h2>
varhar2とnumberをカラムに持つテーブルを作成して、<br>
'1',1<br>
'01',1<br>
'001',1<br>
'123',123<br>
を登録する。<br>
<pre class="prompt">
SQL> create table char_number (col_char varchar2(10) not null, col_number number(10) not null);
Table CHAR_NUMBERは作成されました。
SQL> insert into char_number values ('1', 1);
1行挿入しました。
SQL> insert into char_number values ('123', 123);
1行挿入しました。
SQL> insert into char_number values ('01', 1);
1行挿入しました。
SQL> insert into char_number values ('001', 1);
1行挿入しました。
SQL> create unique index pk_char_number on char_number (col_char);
Index PK_CHAR_NUMBERは作成されました。
SQL> create index ind_char_number_col_number on char_number (col_number);
Index IND_CHAR_NUMBER_COL_NUMBERは作成されました。
SQL> alter table char_number add constraint pk_char_number primary key (col_char) using index;
Table CHAR_NUMBERが変更されました。
</pre>
<br>
各々、カラムと異なる型で検索する場合と、カラムと同じ型で検索する場合の実行計画を確認してみよう。<br>
<h2><span style="color:gray">文字列のカラムを数値で検索する場合</span></h2>
<pre class="prompt">
SQL> set autotrace on explain
自動トレース有効
実行計画のみを表示します。
SQL> select col_number from char_number where col_char = 1;
COL_NUMBER
_____________
1
1
1
Explain Plan
-----------------------------------------------------------
PLAN_TABLE_OUTPUT
________________________________________________________________________________________________________
Plan hash value: 4249545857
-----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 60 | 2 (0)| 00:00:01 |
| 1 | VIEW | index$_join$_001 | 3 | 60 | 2 (0)| 00:00:01 |
|* 2 | HASH JOIN | | | | | |
| 3 | INDEX FAST FULL SCAN| IND_CHAR_NUMBER_COL_NUMBER | 3 | 60 | 1 (0)| 00:00:01 |
|* 4 | INDEX FAST FULL SCAN| PK_CHAR_NUMBER | 3 | 60 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access(ROWID=ROWID)
4 - filter(TO_NUMBER("COL_CHAR")=1)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
</pre>
<br>
この場合は、右辺の検索値ではなく、テーブル内のカラムの値が暗黙の型変換の対象になってしまう。<br>
なので、インデックスが使われなくなる。これはパフォーマンスに大きな影響がある。<br>
その上、'1','01','001'が、すべて1として評価されてしまうため、実際に検索したいことと異なってしまっている可能性もある。<br>
<h2><span style="color:gray">数値のカラムを文字列で検索する場合</span></h2>
<pre class="prompt">
SQL> select col_number from char_number where col_number = '1';
COL_NUMBER
_____________
1
1
1
Explain Plan
-----------------------------------------------------------
PLAN_TABLE_OUTPUT
__________________________________________________________________________________________________
Plan hash value: 2512486424
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 39 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IND_CHAR_NUMBER_COL_NUMBER | 3 | 39 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("COL_NUMBER"=1)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
</pre>
<br>
この場合は、右辺の検索値が暗黙の型変換の対象になる。インデックスも使われるために特に問題にはならないことが多いだろう。<br>
<h2><span style="color:gray">結局</span></h2>
これは数値から文字列への暗黙変換は変換先を一意に特定できないからだ。<br>
'1','01','001'が、すべて1として評価されてしまったことから考えると、その逆ができないということは理解しやすい。<br>
だから、文字列から数値への暗黙変換は発生するけど、その逆は発生しない。<br>
<br>
とはいえ、数値のカラムは素直に数値で検索した方がいいだろう。<br>
わざわざ文字列で検索する必要があるとは考えにくい。<br>
<br>
最後にカラムと同じ型で実行した場合を見てみよう。<br>
<h2><span style="color:gray">文字列のカラムを文字列で検索する場合</span></h2>
<pre class="prompt">
SQL> select col_number from char_number where col_char = '1';
COL_NUMBER
_____________
1
Explain Plan
-----------------------------------------------------------
PLAN_TABLE_OUTPUT
_________________________________________________________________________________________________
Plan hash value: 4007525316
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 20 | 1 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID| CHAR_NUMBER | 1 | 20 | 1 (0)| 00:00:01 |
|* 2 | INDEX UNIQUE SCAN | PK_CHAR_NUMBER | 1 | | 0 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("COL_CHAR"='1')
</pre>
<h2><span style="color:gray">数値のカラムを数値で検索する場合</span></h2>
<pre class="prompt">
SQL> select col_number from char_number where col_number = 1;
COL_NUMBER
_____________
1
1
1
Explain Plan
-----------------------------------------------------------
PLAN_TABLE_OUTPUT
__________________________________________________________________________________________________
Plan hash value: 2512486424
-----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 3 | 39 | 1 (0)| 00:00:01 |
|* 1 | INDEX RANGE SCAN| IND_CHAR_NUMBER_COL_NUMBER | 3 | 39 | 1 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - access("COL_NUMBER"=1)
Note
-----
- dynamic statistics used: dynamic sampling (level=2)
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-90883936320417485992020-08-24T12:00:00.039+09:002020-08-24T12:00:03.334+09:00[Oracle] autotrace traceonly explain で表示されるSQL_IDが変わってしまう<h2><span style="color:gray">SQL_ID取得</span></h2>
<a href="https://blog.sgnet.co.jp/2020/08/oracle-sqlplussqlid.html">[Oracle] SQL*PlusでSQL_IDを取得する</a>では、sqlplusの set feedback on sql_id を使って特定SQLのSQLIDを取得する方法を紹介した。<br>
<br>
<pre class="prompt">
SQL> set feedback on sql_id
SQL> select sysdate from dual;
SYSDATE
---------
15-AUG-20
1 row selected.
SQL_ID: 7h35uxf5uhmm1
</pre>
<br>
<h2><span style="color:gray">オートトレース</span></h2>
autotrace traceonly explain にすると、SQL_IDが結果の先頭行に表示される。<br>
<br>
<pre class="prompt">
SQL> set autotrace traceonly explain
SQL> select sysdate from dual;
SQL_ID: 7h35uxf5uhmm1
SQL_ID: g72kdvcacxvtf
Execution Plan
----------------------------------------------------------
SQL_ID: 7a9pkyu8zp8bc
Plan hash value: 1388734953
-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------
SQL_ID: 3s1hh8cvfan6w
SQL_ID: g72kdvcacxvtf
</pre>
<br>
traceonly explainではない、通常のオートトレースでもやってみよう。<br>
<br>
<pre class="prompt">
SQL> set autotrace on
SYSDATE
---------
15-AUG-20
1 row selected.
SQL_ID: 7h35uxf5uhmm1
SQL_ID: g72kdvcacxvtf
Execution Plan
----------------------------------------------------------
SQL_ID: 7a9pkyu8zp8bc
Plan hash value: 1388734953
-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------
SQL_ID: 3s1hh8cvfan6w
SQL_ID: g72kdvcacxvtf
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
579 bytes sent via SQL*Net to client
366 bytes received via SQL*Net from client
3 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
1 rows processed
</pre>
<br>
1 row selected. に続くトレース結果の先頭行にSQL_IDが表示されている。<br>
<br>
<h2><span style="color:gray">共有プールをクリア</span></h2>
いずれも先頭に SQL_ID: 7h35uxf5uhmm1 が表示されていて、これが select sysdate from dual のSQL_IDとなっている。<br>
ところが、共有プールをクリアしてから set autotrace traceonly explain で実行すると結果が変わってくる。<br>
<br>
<pre class="prompt">
SQL> alter system flush shared_pool;
System FLUSHが変更されました。
</pre>
<br>
<pre class="prompt">
SQL> set autotrace traceonly explain
SQL> select sysdate from dual;
SQL_ID: g72kdvcacxvtf
SQL_ID: g72kdvcacxvtf
Execution Plan
----------------------------------------------------------
SQL_ID: 7a9pkyu8zp8bc
Plan hash value: 1388734953
-----------------------------------------------------------------
| Id | Operation | Name | Rows | Cost (%CPU)| Time |
-----------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 2 (0)| 00:00:01 |
| 1 | FAST DUAL | | 1 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------
SQL_ID: 3s1hh8cvfan6w
SQL_ID: g72kdvcacxvtf
</pre>
<br>
結果の先頭行が SQL_ID: 7h35uxf5uhmm1 ではなくなっている。<br>
<br>
<h2><span style="color:gray">SQLを実際に実行しているか?</span></h2>
v$sql の実行回数を見ると<br>
<br>
<pre class="prompt">
SQL> set feedback off
SQL> set autotrace off
SQL> select executions from v$sql where sql_id = '7h35uxf5uhmm1';
EXECUTIONS
----------
0
</pre>
<br>
select sysdate from dual の実行回数が0回になっている。<br>
<br>
<pre class="prompt">
SQL> select executions from v$sql where sql_id = '7h35uxf5uhmm1';
EXECUTIONS
----------
0
</pre>
<br>
当然、通常はSQLを実行する度にexecutionsの値が増えていく。<br>
<br>
<pre class="prompt">
SQL> select from sysdate from dual;
SYSDATE
---------
15-AUG-20
SQL> select executions from v$sql where sql_id = '7h35uxf5uhmm1';
EXECUTIONS
----------
1
SQL> select from sysdate from dual;
SYSDATE
---------
15-AUG-20
SQL> select executions from v$sql where sql_id = '7h35uxf5uhmm1';
EXECUTIONS
----------
2
</pre>
<br>
これは autotrace traceonly explain ではSQLの実行計画は生成するが、実際にはSQLを実行しないことが原因のようだ。<br>
<br>
<h2><span style="color:gray">select以外</span></h2>
ちなみに、autotrace traceonly explain でもselect以外のinsert, update, deleteは実際にSQLが実行されてしまうので、要注意。
Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-1473841628018226702020-08-03T12:00:00.002+09:002020-08-03T12:00:02.900+09:00[Oracle] SQL*PlusでSQL_IDを取得する<h2><span style="color:gray">set feedback on sql_id</span></h2>
Oracleの18.1から、SQL*PlusでSQL_IDを取得することができるようになっている。<br>
<a href="https://docs.oracle.com/cd/E96517_01/sqpug/release-changes.html#GUID-EB3936C8-5BD2-4A9F-A859-13B23DB4B5E4">SQL*Plusリリース18c, バージョン18.1での変更点</a><br>
<br>
<a name='more'></a>
<pre class="prompt">
SQL> set rowlimit 1
SQL> set feedback on sql_id
SQL> select sysdate from dual;
SYSDATE
---------
25-JUL-20
1 row selected. (rowlimit reached)
SQL_ID: 7h35uxf5uhmm1
</pre>
<br>
<h2><span style="color:gray">set rowlimit</span></h2>
上記の例では結果は必ず1行しか返ってこないが、多くの行が返ってきてしまう環境では、同じく18.1から導入された set rowlimit 1 を使えば、SQLを変えることなく、1行のみを取得することができる。<br>
特定SQLのSQL_IDのみを知りたい場合は、こちらを使えばいい。<br>
<br>
<h2><span style="color:gray">SQL_ID</span></h2>
SQL_IDはSQLの文字列からハッシュ関数で生成される固定値で、Oracleのバージョンが変わらない限りは変わらない。<br>
このため、V$SQL等から情報を得るために、事前にアプリケーションで実装しているSQLのSQL_IDを一覧にしておくと役に立つ。<br>
この場合は、テーブル定義DDLのみを流した空のDB環境で実施すると、時間がかからなくていいだろう。<br>
<br>
ちなみに、sqlclの19.1でも、set feedback on sql_id を試してみたが、残念ながらこちらには実装されていない模様。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-36780276521948139272020-07-20T12:00:00.001+09:002020-07-20T12:00:01.940+09:00[PowerShell] Unixシェルスクリプトとの比較 その2<a href="https://blog.sgnet.co.jp/2020/07/powershell-unix.html">Unixシェルスクリプトとの比較 その1</a>の続き<br>
<br>
<h2><span style="color:gray">sed</span></h2>
sedで文字列置換。<br>
<pre class="prompt">
$ printf "apple\nbanana\norange\n" | sed "s/an/xyz/g"
apple
bxyzxyza
orxyzge
</pre>
<pre class="prompt">
PS > "apple","banana","orange" | % { $_ -replace "an", "xyz" }
apple
bxyzxyza
orxyzge
</pre>
<br>
-replace演算子も配列に直接適用することができる。<br>
<pre class="prompt">
PS > "apple","banana","orange" -replace "an", "xyz"
apple
bxyzxyza
orxyzge
</pre>
<h2><span style="color:gray">awk</span></h2>
<br>
1 2 3 を2乗したものを出力してみる。<br>
<pre class="prompt">
bash$ seq 3 | awk 'BEGIN{ print "begin." } {print $1*$1 } END{ print "end." }'
begin.
1
4
9
end.
</pre>
<pre class="prompt">
PS > 1..3 | % -begin{ "begin." } -process{ $_*$_ } -end{ "end." }
begin.
1
4
9
end.
</pre>
<br>
-begin, -process, -end は明示的に書かなくても、{}ブロックを3つ書くと順番に解釈される。<br>
<pre class="prompt">
PS > 1..3 | % { "begin." } { $_*$_ } { "end." }
begin.
1
4
9
end.
</pre>
<br>
残念ながら、beginだけ、endだけというのはできないので空のprocessブロックを置く必要がある。<br>
<pre class="prompt">
PS > 1..3 | % -end{ "end." }
コマンド パイプライン位置 1 のコマンドレット ForEach-Object
次のパラメーターに値を指定してください:
Process[0]:
end.
PS > 1..3 | % -end{ "end." } {}
end.
</pre>
<br>
% は ForEach-Object のエイリアスなので、以下のように書いても同じ。<br>
<pre class="prompt">
PS > 1..3 | foreach-object { "begin." } { $_*$_ } { "end." }
begin.
1
4
9
end.
</pre>
<br>
awkのスクリプトファイルを作るように、フィルタ処理を名前付きのコマンドレットにすることができる。<br>
<pre class="prompt">
PS > filter square() {
>> begin { "begin." }
>> process { $_*$_ }
>> end { "end." }
>> }
PS > 1..3 | square
begin.
1
4
9
end.
</pre>
<br>
フィールドの分割<br>
<pre class="prompt">
bash$ printf "abc def ghi\n"
abc def ghi
bash$ printf "abc def ghi\n" | awk $'{ print "(\'" $1 "\',\'" $2 "\',\'" $3 "\')" }'
('abc','def','ghi')
</pre>
<pre class="prompt">
PS > "abc def ghi"
abc def ghi
PS > "abc def ghi" | % { $1,$2,$3=$_.Split(" "); "('$1','$2','$3')" }
('abc','def','ghi')
</pre>
<br>
PowerShellでは、%というエイリアスや、filterというキーワードが登場するほど、フィルタが自然な概念になっているのが面白い。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-15808273569869044822020-07-06T12:00:00.001+09:002020-07-06T12:00:19.553+09:00[PowerShell] Unixシェルスクリプトとの比較 その1シェルに慣れ親しんでいるUnix/LinuxユーザがPowerShellを使おうとしたときに、ちょっととっつきにくいところがある。<br>
それは、Unixではgrepやsedといった個別のコマンドが担っている概念をPowerShellでは言語が担っているというところじゃないだろうか。<br>
この考え方を受け入れると、PowerShellが昔ながらのやり方を受け継ぎつつも、それをさらに洗練しているように見えてくる。<br>
<br>
Unixシェルでよく使われるコマンドとPowerShellがどう対応しているのか見てみよう。<br>
<a name='more'></a>
<h2><span style="color:gray">/dev/null</span></h2>
コマンドと言っておいて、いきなりファイル。<br>
<pre class="prompt">
bash$ echo foo
foo
bash$ echo foo >/dev/null
</pre>
<pre class="prompt">
PS > echo foo
foo
PS > echo foo | out-null
</pre>
<br>
out-nullはファイルではなく、コマンドレットなのでリダイレクトではなく、パイプを使う。<br>
<h2><span style="color:gray">printf</span></h2>
<pre class="prompt">
bash$ printf "%05d\n" 123
00123
</pre>
<pre class="prompt">
PS > "{0:D5}" -f 123
00123
</pre>
<br>
繰り返し<br>
<pre class="prompt">
bash$ printf "%3s\n" | sed 's/\s/abc/g'
abcabcabc
</pre>
<pre class="prompt">
PS > "abc" * 3
</pre>
<br>
繰り返しはPowerShellのほうが直感的で簡単。<br>
<h2><span style="color:gray">seq</span></h2>
<pre class="prompt">
bash$ seq 3
1
2
3
</pre>
<pre class="prompt">
PS > 1..3
1
2
3
</pre>
<br>
1..3のようにそのまま書くだけ。<br>
<h2><span style="color:gray">head</span></h2>
<pre class="prompt">
bash$ seq 10 | head -n3
1
2
3
</pre>
<pre class="prompt">
PS > 1..10 | select-object -first 3
1
2
3
</pre>
<h2><span style="color:gray">tail</span></h2>
<pre class="prompt">
bash$ seq 10 | tail -n3
8
9
10
</pre>
<pre class="prompt">
PS > 1..10 | select-object -last 3
8
9
10
</pre>
<h2><span style="color:gray">grep</span></h2>
条件に一致する要素のみの抽出。<br>
<pre class="prompt">
bash$ printf "apple\nbanana\norange\n"
apple
banana
orange
bash$ printf "apple\nbanana\norange\n" | grep an
banana
orange
</pre>
<br>
PowerShellでは改行とかの特殊文字を \ ではなく、`(バッククォート)を使って表現する。<br>
<pre class="prompt">
PS > "apple`nbanana`norange`n"
apple
banana
orange
PS > "apple`nbanana`norange`n" | ? { $_ -match "an" }
apple
banana
orange
</pre>
<br>
でも、これはうまくいかない。PowerShellのパイプは行指向ではなく、オブジェクトとしての配列を処理するからだ。<br>
カンマ区切りでリストを作成してやってみよう。<br>
<pre class="prompt">
PS > "apple","banana","orange" | ? { $_ -match "an" }
banana
orange
PS > "apple","banana","orange"
apple
banana
orange
</pre>
<br>
テキストがファイルに保存されている場合はcatが行を要素にした配列に変換してくれる。<br>
<pre class="prompt">
PS > cat .\fruits.txt
apple
banana
orange
PS > cat .\fruits.txt | ? { $_ -match "an" }
banana
orange
</pre>
<br>
? はWhere-Objectというコマンドレットのエイリアスなので、以下でも同じ。<br>
<pre class="prompt">
PS > "apple","banana","orange" | where-object { $_ -match "an" }
banana
orange
</pre>
<br>
ところで、-match演算子は左辺に配列を取ることができる。スクリプト中でデータがパイプ経由ではなく、配列で存在する場合はこちらの書き方もよさそう。<br>
<pre class="prompt">
PS > "apple","banana","orange" -match "an"
banana
orange
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-19922358909887522062020-06-22T12:00:00.000+09:002020-06-22T12:00:01.112+09:00[Oracle] OracleのNUMBER型の謎<h2><span style="color:gray">PLS_INTEGER</span></h2>
OracleのPL/SQLにはテーブルには使えない数値型が存在する。<br>
これはNUMBER型のような10進型ではなく、Cのintのような内部2進の32ビット整数型なので高速に動作する。<br>
<a name='more'></a>
<pre class="prettyprint linenums">
declare
i_number number := 0;
i_pls_integer pls_integer := 0;
start_time number;
elapsed_time number;
begin
start_time := dbms_utility.get_time();
for i in 1..10000000 loop
i_number := i_number + 1;
end loop;
elapsed_time := dbms_utility.get_time() - start_time;
dbms_output.put_line(elapsed_time * 10);
start_time := dbms_utility.get_time();
for i in 1..10000000 loop
i_pls_integer := i_pls_integer + 1;
end loop;
elapsed_time := dbms_utility.get_time() - start_time;
dbms_output.put_line(elapsed_time * 10);
end;
/
-----
210
70
</pre>
<br>
PL/SQL内で一時的に使う数値で、PLS_INTEGERの範囲に収まるのであれば、NUMBERではなくPLS_INTEGERの使用を検討するといいかもしれない。<br>
<br>
<h2><span style="color:gray">NUMBER? INTEGER?</span></h2>
PLS_INTEGERと同じように、NUMBERとINTEGERの動作速度を比較してみよう。<br>
<pre class="prettyprint linenums">
declare
i_integer integer := 0;
start_time number;
elapsed_time number;
begin
start_time := dbms_utility.get_time();
for i in 1..10000000 loop
i_integer := i_integer + 1;
end loop;
elapsed_time := dbms_utility.get_time() - start_time;
dbms_output.put_line(elapsed_time * 10);
end;
/
-----
410
</pre>
<br>
INTEGERは標準SQLの整数型をサポートするためのエイリアスで実際には小数部が0桁のNUMBERだ。<br>
<br>
<a href="https://docs.oracle.com/cd/F19136_01/sqlrf/Data-Types.html#GUID-0BC16006-32F1-42B1-B45E-F27A494963FF">https://docs.oracle.com/cd/F19136_01/sqlrf/Data-Types.html#GUID-0BC16006-32F1-42B1-B45E-F27A494963FF</a><br>
<br>
NUMBERよりINTEGERが遅くなっている。なんで、こんな違いが出るんだろう。<br>
INTEGERではなく、明示的にNUMBER(38, 0)でやってみよう。<br>
<pre class="prettyprint linenums">
declare
i_number38 number(38) := 0;
start_time number;
elapsed_time number;
begin
start_time := dbms_utility.get_time();
for i in 1..10000000 loop
i_number38 := i_number38 + 1;
end loop;
elapsed_time := dbms_utility.get_time() - start_time;
dbms_output.put_line(elapsed_time * 10);
end;
/
-----
410
</pre>
<br>
うーん、INTEGERの場合と同じだね。<br>
どうも、桁数を指定しないNUMBERは特別みたい。<br>
<br>
<h2><span style="color:gray">NUMBER(p,s)を確認</span></h2>
ちょっとNUMBER(p,s)の挙動を確認。<br>
<pre class="prettyprint linenums">
declare
n number(4,2);
begin
n := 12.344;
dbms_output.put_line(n);
n := 12.345;
dbms_output.put_line(n);
n := 99.994;
dbms_output.put_line(n);
n := 99.995;
dbms_output.put_line(n);
end;
/
12.34
12.35
99.99
ORA-06502: PL/SQL: 数値または値のエラー: 数値の精度が大きすぎます。が発生しました
</pre>
<br>
マニュアルを見ると、<br>
<br>
<a href="https://docs.oracle.com/cd/F19136_01/sqlrf/Data-Types.html#GUID-75209AF6-476D-4C44-A5DC-5FA70D701B78">https://docs.oracle.com/cd/F19136_01/sqlrf/Data-Types.html#GUID-75209AF6-476D-4C44-A5DC-5FA70D701B78</a><br>
「値が精度の有効範囲を超えると、Oracleはエラーを戻します。値が位取りの有効範囲を超えると、Oracleはその値を丸めます。」<br>
<br>
桁数をチェックしているのではなく、数値がスケール指定した固定小数点数の範囲に収まるかどうかのチェックになっている。<br>
<br>
<h2><span style="color:gray">素のNUMBERは?</span></h2>
では、桁数とスケールを明示しない素のNUMBERでやってみよう。<br>
<pre class="prettyprint linenums">
declare
n_max number := 9.999999999999999999999999999999999999999E+125;
n_round number := n_max + 4E+85;
n_over number := n_max + 5E+85;
d_max varchar2(256);
d_round varchar2(256);
d_over varchar2(256);
begin
select dump(n_max, 16), dump(n_round, 16), dump(n_over, 16)
into d_max, d_round, d_over from dual;
dbms_output.put_line(n_max);
dbms_output.put_line(d_max);
dbms_output.put_line(n_round);
dbms_output.put_line(d_round);
dbms_output.put_line(n_over);
dbms_output.put_line(d_over);
end;
/
9.999999999999999999999999999999999999999000000000000000000000000000000000000000000000000000000E+125
Typ=2 Len=21: ff,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64
9.999999999999999999999999999999999999999000000000000000000000000000000000000000000000000000000E+125
Typ=2 Len=21: ff,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64
~
Typ=2 Len=2: ff,65
</pre>
<br>
前出のマニュアルを再度見ると、<br>
<br>
<a href="https://docs.oracle.com/cd/F19136_01/sqlrf/Data-Types.html#GUID-75209AF6-476D-4C44-A5DC-5FA70D701B78">https://docs.oracle.com/cd/F19136_01/sqlrf/Data-Types.html#GUID-75209AF6-476D-4C44-A5DC-5FA70D701B78</a><br>
「Oracleは、100進数で20桁までの精度で数値の移植性を保証します。」<br>
<br>
とあるので、1バイトで10進2桁を保持して、20バイトで40桁まで保持できることになる。<br>
<br>
つまり、NUMBER(p,s)形式の型は代入時に毎回範囲チェックをしている。<br>
素のNUMBERは範囲チェックをせずにあふれた分は切り捨ててしまう。<br>
さらにオーバーフローしてもエラーにならずにオーバーフローを表す値になってしまう。<br>
マニュアルに記載されている「精度および位取りを指定しない場合、最大の範囲および精度をOracleの数値に指定したことになります。」の意味がやっと分かった。<br>
<br>
NUMBERとINTEGERの動作速度の違いはここから来ているんだね。<br>
<br>
OracleのNUMBER型について、やっと納得できたような気がする。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-17476510466202173252020-06-08T12:00:00.000+09:002020-06-08T12:00:00.416+09:00[bash] コマンドの前で変数への代入をするbashでコマンドの前で変数への代入をする構文を正確に理解してなかったので、整理。<br>
<br>
<h2><span style="color:gray">LC_ALL=C sort ...</span></h2>
よく見かけるのはこのパターン。<br>
<pre class="prompt">
$ LC_ALL=C sort ...
</pre>
<br>
ソート条件に言語設定が影響してくるのを防ぐのと、パフォーマンス対策で事前にLC_ALLをCにしておく。<br>
manapgeの説明によると、直接影響するのはLC_COLLATEやLC_CTYPEとあるけど、LC_ALLを設定すると他のLC_*系の値より優先される。<br>
<br>
<a name='more'></a>
<h2><span style="color:gray">i=foo echo $i</span></h2>
同じ様な感じでも、これはうまくいかない。<br>
<pre class="prompt">
$ i=foo echo $i
</pre>
<br>
i=fooの代入が実行される前のパース時に$iが先に展開されるからだね。<br>
<br>
もうちょっと明示するとこうなる。<br>
<pre class="prompt">
$ i=bar
$ i=foo echo $i
bar
</pre>
<br>
fooではなく、barが表示される。<br>
<br>
<h2><span style="color:gray">環境変数</span></h2>
manpageを見てみると、こうある。<br>
<br>
「単純なコマンドの展開」<br>
変数代入の = の後にあるテキストに対して、チルダ展開、 パラメータ展開、コマンド置換、算術式展開、クォート削除が行われます。 この処理は変数を代入する前に行われます。 <br>
<br>
コマンド名が残らなかった場合には、 変数を代入した結果が現在のシェル環境に効果を及ぼします。 それ以外の場合、変数は実行されるコマンドの環境に追加されるだけで、 現在のシェル環境には影響を与えません。<br>
<br>
なので、環境変数を一時的に変更してコマンドを実行したいけど、以降には影響を与えたくないときにこの構文を使う。<br>
<pre class="prompt">
$ cat echo_test.sh
#!/bin/sh
echo $TEST
$ export TEST=foo
$ echo $TEST
foo
$ TEST=bar ./echo_test.sh
bar
$ echo $TEST
foo # fooのままになってる。
</pre>
<br>
セミコロンを入れると文が切れてしまって、違う意味になるので注意。<br>
<pre class="prompt">
$ TEST=bar; ./echo_test.sh
bar
$ echo $TEST
bar # barに変わってしまう!
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-43923495971158935102020-05-25T12:00:00.000+09:002020-05-25T12:00:03.271+09:00[Oracle] select句のアスタリスクと擬似列<h2><span style="color:gray">取得日時も付加しておきたい</span></h2>
Oracleでv$sessionのような動的ビューのログを取るときなんかに、取得日時も付加しておきたい場合がある。<br>
<br>
こんな感じ
<pre class="prettyprint linenums">
select to_char(sysdate, 'YYYY-MM-DD HH24:MI:SS'), * from v$session;
</pre>
<br>
でも、これはエラーになってしまう。<br>
<a name='more'></a>
テーブルは1つしか参照していないのだから、いいじゃんって気もするが、だめなのだ。<br>
<h2><span style="color:gray">では、どうするか</span></h2>
アスタリスクの前にテーブル名を明示する。<br>
<pre class="prettyprint linenums">
select to_char(sysdate, 'YYYY-MM-DD HH24:MI:SS'), v$session.* from v$session;
</pre>
<br>
灯台下暗しといった感じだけど、これは標準SQLの書き方なんだろうか。<br>
ちょっと調べてみたところ、SQLiteとMySQLではサポートされているようですが...Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-86095693130718132642020-05-11T12:00:00.001+09:002020-05-11T12:00:00.190+09:00[Oracle] DockerにSQL*Plus, SQL*Loaderがつながらない<h2><span style="color:gray">つながらないのだ</span></h2>
Oracle 19cをDockerで動作させて、ネットワーク越しに(というかコンテナ外から)SQL*Plus, SQL*Loaderでつなげようとしたら、つながらない現象が起きた。<br>
<br>
SQL*Plus
<pre class="prompt">
$ sqlplus user/password@localhost/pdb1
SQL*Plus: Release 19.0.0.0.0 - Production on Sat May 2 20:11:32 2020
Version 19.5.0.0.0
Copyright (c) 1982, 2019, Oracle. All rights reserved.
ERROR:
ORA-12637: Packet receive failed
</pre>
<a name='more'></a>
<br>
SQL*Loader
<pre class="prompt">
$ sqlldr control=tbl1.ctl,userid=user/password@localhost/pdb1
SQL*Loader: Release 19.0.0.0.0 - Production on Sat May 2 19:36:11 2020
Version 19.5.0.0.0
Copyright (c) 1982, 2019, Oracle and/or its affiliates. All rights reserved.
SQL*Loader-704: Internal error: ulconnect: OCIServerAttach [0]
ORA-12637: Packet receive failed
</pre>
<br>
ちなみにOacleとの接続は普通にポートフォワードで行っている。
<pre class="prompt">
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
84ff35f3125e oracle/database:19.3.0-ee "/bin/sh -c 'exec $O…" 8 months ago Up 42 seconds (health: starting) 0.0.0.0:1521->1521/tcp, 0.0.0.0:5500->5500/tcp ora1930
</pre>
<h2><span style="color:gray">OOB?</span></h2>
どうも、エラーメッセージからしてTCP/IPレベルでエラーが起きているようだけど、いろいろ調べた結果、クライアント側のsqlnet.oraの設定でつながるようになった。<br>
<br>
instantclient_19_5/network/admin/sqlnet.ora
<pre class="prettyprint linenums">
DISABLE_OOB=on
</pre>
OOBを無効にすればいいらしい。DISABLEをonにするから無効。ややこしい。<br>
で、OOBって何だ?という話なんだけど、Out Of Bandの略で帯域外の通信のことらしい。
特にTCPの場合はURGフラグによる緊急パケットのこと。<br>
<a href="https://www.wdic.org/w/WDIC/URG%E3%83%91%E3%82%B1%E3%83%83%E3%83%88">URGパケット - 通信用語の基礎知識</a>
<br>
実際にはこれがどういうときに使われるかというと、例えば以下のようにSQL*Plusで時間のかかる処理を実行中にCtrl-cで処理を中断したときに、サーバに処理の中断を伝える場合だ。
<pre class="prompt">
SQL< exec dbms_session.sleep(10);
^CBEGIN dbms_session.sleep(10); END;
*
ERROR at line 1:
ORA-01013: user requested cancel of current operation
ORA-06512: at "SYS.DBMS_SESSION", line 435
ORA-06512: at line 1
</pre>
このときに、OOBが有効だと、URGパケットを使って、優先的に中断要求が伝えられる。<br>
OOBが無効だと、通常のパケットの優先順位で伝えられるため、中断に時間がかかる場合があるみたい。<br>
結局、Docker(のバージョンによっては?)のポートフォワードでは、TCPのOut Of Bandをうまくハンドリングしてくれていないことが原因のようです。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-41832112741613790072020-05-11T12:00:00.000+09:002020-05-11T12:00:06.212+09:00[bash] 標準エラー出力をフィルタリング<h2><span style="color:gray">標準エラー出力なんとかしたい</span></h2>
bashでの作業中に標準エラー出力に、実害のない警告メッセージが延々と出てくることがないだろうか。<br>
他のエラーメッセージが紛れてしまうので、これを除去したい。
<pre class="prompt">
$ somecommand
わかってるからもういいよ
なんかエラー!
わかってるからもういいよ
</pre>
<a name='more'></a>
<br>
標準出力であれば、以下のように grep -v で特定のメッセージを取り除くことができる。
<pre class="prompt">
$ somecommand | grep -v 'わかってるからもういいよ'
なんかエラー!
</pre>
<br>
でも、これは標準エラー出力に出ているのだ。
<h2><span style="color:gray">プロセス置換を使う</span></h2>
こういう時はプロセス置換を使う。
<pre class="prompt">
$ somecommand 2> >(grep -v 'わかってるからもういいよ' >&2)
なんかエラー!
</pre>
これで標準エラー出力をフィルタリングできた!Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-36811074656319829552020-04-27T12:00:00.000+09:002020-04-27T12:00:01.855+09:00[Java] スタティックイニシャライザでのエラー処理<a href="https://blog.sgnet.co.jp/2020/03/java-jdbc.html">[Java] JDBCドライバもサービスプロバイダーでできてる</a>ではスタティックイニシャライザ内でDrivaerManagerにJDBCドライバを登録した。<br>
しかしながら、DriverManager.registerDriverはSQLExceptionをスローするため、チェック例外として処理しなければいけない。<br>
<br>
この場合、どのようにエラーハンドリングするべきだろうか。<br>
<a name='more'></a>
<h2><span style="color:gray">ExceptionInInitializerError</span></h2>
MyDriver.javaからスタティックイニシャライザを抜き出してみると<br>
<pre class="prettyprint linenums">
public class MyDriver implements Driver {
static {
try {
DriverManager.registerDriver(new MyDriver());
} catch(SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
</pre>
ExceptionInInitializerErrorというエラーが標準で用意されているので、それをスローすればいいみたい。<br>
ふーむ、こんなのがあるんだね。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-25501622077802741672020-04-13T12:00:00.000+09:002020-04-13T12:00:04.149+09:00[Java] サービスプロバイダーを作ってみよう 〜Java9以降〜<h2><span style="color:gray">Java9のモジュール機構</span></h2>
<a href="https://blog.sgnet.co.jp/2020/03/java-java8.html">サービスプロバイダーを作ってみよう Java8以前</a>ではJavaのサービスプロバイダーを作った。<br>
このときはjarの中にMETA-INF/services/ファイルを配置したけど、Java9以降ではこの辺に新しいやり方が導入されている。<br>
まずはJava8以前と同様にサービス、サービスプロバイダー、アプリケーションを作っていこう。<br>
<a name='more'></a>
※.java自体はJava8以前と同じ。<br>
<h2><span style="color:gray">サービス</span></h2>
まずはサービスのインターフェースを定義する。<br>
local/api/MyService.java<br>
<pre class="prettyprint linenums">
package local.api;
public interface MyService {
void show();
}
</pre>
Java8と違うところは、MyServiceをjarの外部に公開するためにmodule-info.javaを用意するところ。<br>
META-INF/servicesと違いコンパイル時に必要となる。<br>
module-info.java
<pre class="prettyprint linenums">
module local.api {
exports local.api;
uses local.api.MyService;
}
</pre>
これによってlocal.apiパッケージのMyServiceがjarの外部に公開される。<br>
これをコンパイルしてjarにする。<br>
<pre class="prompt">
$ javac -d bin src/*.java src/local/api/*.java
$ jar --create --file local-api.jar -C bin .
</pre>
<h2><span style="color:gray">サービスプロバイダー</span></h2>
次にサービスプロバイダー。試しに2つ作ってみる。<br>
local/provider/MyServiceProvider1.java
<pre class="prettyprint linenums">
package local.provider;
import local.api.MyService;
public class MyServiceProvider1 implements MyService {
@Override public void show() {
System.out.println("MyService1");
};
}
</pre>
local/provider/MyServiceProvider2.java
<pre class="prettyprint linenums">
package local.provider;
import local.api.MyService;
public class MyServiceProvider2 implements MyService {
@Override public void show() {
System.out.println("MyService2");
};
}
</pre>
これもMETA-INF/servicesではなく、module-info.javaを作成する。<br>
今度はrequiresで外部から取り込むパッケージを指定して、providesでサービスを実装するサービスプロバイダーを指定する。<br>
module-info.java
<pre class="prettyprint linenums">
module local.provider {
requires local.api;
provides local.api.MyService with local.provider.MyServiceProvider1, local.provider.MyServiceProvider2;
}
</pre>
これをコンパイルしてjarにする。<br>
<pre class="prompt">
$ javac -d bin --module-path ../api src/*.java src/local/provider/*.java
$ jar --create --file local-provider.jar -C bin .
</pre>
<h2><span style="color:gray">アプリケーション</span></h2>
最後にサービスプロバイダーを実行するアプリケーションを作成する。<br>
local/app/Main.java
<pre class="prettyprint linenums">
package local.app;
import java.util.ServiceLoader;
import local.api.MyService;
public class Main {
public static void main(String ... args) {
for (MyService service : ServiceLoader.load(MyService.class)) {
System.out.println(service.getClass().getName());
service.show();
}
}
}
</pre>
module-info.java
<pre class="prettyprint linenums">
module local.app {
requires local.api;
uses local.api.MyService;
}
</pre>
<pre class="prompt">
$ javac -d bin --module-path ../api src/*.java src/local/app/*.java
$ jar --create --file local-app.jar -C bin .
</pre>
<h2><span style="color:gray">実行</span></h2>
すべてのjarができたので、実行してみよう。jarをlibに集めておく。
<pre class="prompt">
$ ls lib
local-api.jar local-app.jar local-provider.jar
$ java --module-path lib --module local.app/local.app.Main
local.provider.MyServiceProvider1
MyService1
local.provider.MyServiceProvider2
MyService2
</pre>
<br>
<h2><span style="color:gray">全体のディレクトリイメージ</span></h2>
<pre class="prompt">
$ tree
.
├── api
│ ├── bin
│ │ ├── module-info.class
│ │ └── local
│ │ └── api
│ │ └── MyService.class
│ ├── local-api.jar
│ └── src
│ ├── module-info.java
│ └── local
│ └── api
│ └── MyService.java
├── app
│ ├── bin
│ │ ├── module-info.class
│ │ └── local
│ │ └── app
│ │ └── Main.class
│ ├── local-app.jar
│ └── src
│ ├── module-info.java
│ └── local
│ └── app
│ └── Main.java
├── lib
│ ├── local-api.jar
│ ├── local-app.jar
│ └── local-provider.jar
└── provider
├── bin
│ ├── module-info.class
│ └── local
│ └── provider
│ ├── MyServiceProvider1.class
│ └── MyServiceProvider2.class
├── local-provider.jar
└── src
├── module-info.java
└── local
└── provider
├── MyServiceProvider1.java
└── MyServiceProvider2.java
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-85628312903845081822020-03-30T12:00:00.000+09:002020-03-30T12:00:03.291+09:00[Java] JDBCドライバもサービスプロバイダーでできてる<a href="https://blog.sgnet.co.jp/2020/03/java-java8.html">前回</a>はサービスプロバイダーを作って見たけど、試しにJDBCドライバを実装してみよう(中身はないけど)。<br>
サービスに相当するものはjava.sql.Driverインターフェースとして用意されているので、これを実装する。<br>
<br>
大事なのはスタティックイニシャライザで DriverManager.registerDriver を呼び出して、自分自身を登録すること。<br>
これによって、アプリケーション側が使いたいドライバのクラスを登録する必要がなくなる。<br>
<a name='more'></a>
<h2><span style="color:gray">ドライバ</span></h2>
local.provider.MyDriver.java
<pre class="prettyprint linenums">
package local.provider;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.util.logging.Logger;
import java.util.Properties;
public class MyDriver implements Driver {
static {
try {
DriverManager.registerDriver(new MyDriver());
} catch(SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
@Override public boolean acceptsURL(String url) {
return true;
}
@Override public Connection connect(String url, Properties info) {
return new MyConnection();
}
@Override public int getMajorVersion() {
return 0;
}
@Override public int getMinorVersion() {
return 0;
}
@Override public Logger getParentLogger() {
return null;
}
@Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[] {};
}
@Override public boolean jdbcCompliant() {
return false;
}
}
</pre>
java.sql.Connectionを実装したクラス(local.provider.MyConnection)も必要になるけど、中身は空なので省略。<br>
<br>
META-INF/services/java.sql.Driver
<pre class="prettyprint linenums">
local.provider.MyDriver
</pre>
ビルドして、jarを作る。<br>
<pre class="prompt">
$ javac -d bin -cp .:../api/local-api.jar src/local/provider/*.java
$ jar cvf local-provider.jar -C bin .
</pre>
<h2><span style="color:gray">呼び出し側</span></h2>
local/app/MyService.java
<pre class="prettyprint linenums">
package local.app;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Properties;
import local.api.MyService;
public class Main {
public static void main(String ... args) throws SQLException {
for (Driver d : Collections.list(DriverManager.getDrivers())) {
System.out.println(d);
}
Connection conn = DriverManager.getConnection("jdbc:mydriver/localhost/db", new Properties());
}
}
</pre>
コンパイル
<pre class="prompt">
$ javac -d bin -cp . src/local/app/*.java
$ jar cvf local-app.jar -C bin .
</pre>
実行
<pre class="prompt">
$ java -cp ./lib/local-app.jar:./lib/local-provider.jar local.app.Main
local.provider.MyDriver@266474c2
</pre>
<h2><span style="color:gray">ちなみに</span></h2>
DriverManager.getConnectionはDriverManager.getDriversを呼び出して返ってきたドライバのconnectを呼び出して、最初にnull以外のコネクションを返したドライバを採用しているみたい。
<pre class="prompt">
$ tree
.
├── app
│ ├── bin
│ │ └── local
│ │ └── app
│ │ └── Main.class
│ ├── local-app.jar
│ └── src
│ └── local
│ └── app
│ └── Main.java
├── lib
│ ├── local-app.jar
│ └── local-provider.jar
└── provider
├── bin
│ ├── META-INF
│ │ └── services
│ │ └── java.sql.Driver
│ └── local
│ └── provider
│ └── MyDriver.class
├── local-provider.jar
└── src
└── local
└── provider
└── MyDriver.java
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-18085615918382373052020-03-16T12:00:00.000+09:002020-04-10T21:37:42.284+09:00[Java] サービスプロバイダーを作ってみよう 〜Java8以前〜サービスと言っても、今回やろうとしているのはWebサービスとかではなくJavaが備えているモジュール切り替えの仕組み。<br>
例えば、JDBCドライバは様々なDBMSに対応しているけど、インターフェースに従ってさえいれば、切り替えは比較的容易になっている。<br>
<h2><span style="color:gray">登場人物</span></h2>
登場人物はサービス、サービスプロバイダー、アプリケーションの3つ。<br>
<br>
サービスは実装を伴わず、インターフェースを定義する。<br>
サービスプロバイダーはサービスを実装する。<br>
アプリケーションはサービスを通じてサービスプロバイダーを呼び出す。<br>
<a name='more'></a>
<br>
この3つをそれぞれ別のjarにして、呼び出してみよう。<br>
<h2><span style="color:gray">サービス</span></h2>
まずはサービスのインターフェースを定義する。<br>
local/api/MyService.java<br>
<pre class="prettyprint linenums">
package local.api;
public interface MyService {
void show();
}
</pre>
これをコンパイルしてjarにする。
<pre class="prompt">
$ javac -d bin src/local/api/*.java
$ jar cvf local-api.jar -C bin .
</pre>
<h2><span style="color:gray">サービスプロバイダー</span></h2>
次にサービスプロバイダー。試しに2つ作ってみる。<br>
local/provider/MyServiceProvider1.java
<pre class="prettyprint linenums">
package local.provider;
import local.api.MyService;
public class MyServiceProvider1 implements MyService {
@Override public void show() {
System.out.println("MyService1");
};
}
</pre>
local/provider/MyServiceProvider2.java
<pre class="prettyprint linenums">
package local.provider;
import local.api.MyService;
public class MyServiceProvider2 implements MyService {
@Override public void show() {
System.out.println("MyService2");
};
}
</pre>
先ほど作ったlocal-api.jarを参照してコンパイル。
<pre class="prompt">
$ javac -d bin -cp .:../api/local-api.jar src/local/provider/*.java
</pre>
<br>
jarを作る前にMETA-INF/services/にMyServiceを実装したサービスプロバイダーとして公開するクラスを記載しておく必要がある。<br>
META-INF/services/local.api.MyService
<pre class="prettyprint linenums">
local.provider.MyServiceProvider1
local.provider.MyServiceProvider2
</pre>
<pre class="prompt">
$ jar cvf local-provider.jar -C bin .
</pre>
<h2><span style="color:gray">アプリケーション</span></h2>
最後にサービスプロバイダーを実行するアプリケーションを作成する。<br>
サービスを呼び出すためにはjava.util.ServiceLoaderを使って、特定のサービスインターフェースを実装したサービスプロバイダーを探す必要がある。<br>
local/app/Main.java
<pre class="prettyprint linenums">
package local.app;
import java.util.ServiceLoader;
import local.api.MyService;
public class Main {
public static void main(String ... args) {
for (MyService service : ServiceLoader.load(MyService.class)) {
System.out.println(service.getClass().getName());
service.show();
}
}
}
</pre>
<pre class="prompt">
$ javac -d bin -cp .:../api/local-api.jar src/local/app/*.java
$ jar cvf local-app.jar -C bin .
</pre>
<h2><span style="color:gray">実行</span></h2>
すべてのjarができたので、実行してみよう。jarをlibに集めておく。
<pre class="prompt">
$ ls lib
local-api.jar local-app.jar local-provider.jar
$ java -cp ./lib/local-app.jar:./lib/local-api.jar:./lib/local-provider.jar local.app.Main
local.provider.MyServiceProvider1
MyService1
local.provider.MyServiceProvider2
MyService2
</pre>
<h2><span style="color:gray">全体のディレクトリイメージ</span></h2>
<pre class="prompt">
$ tree
.
├── api
│ ├── bin
│ │ └── local
│ │ └── api
│ │ └── MyService.class
│ ├── local-api.jar
│ └── src
│ └── local
│ └── api
│ └── MyService.java
├── app
│ ├── bin
│ │ └── local
│ │ └── app
│ │ └── Main.class
│ ├── local-app.jar
│ └── src
│ └── local
│ └── app
│ └── Main.java
├── lib
│ ├── local-api.jar
│ ├── local-app.jar
│ └── local-provider.jar
└── provider
├── bin
│ ├── META-INF
│ │ └── services
│ │ └── local.api.MyService
│ └── local
│ └── provider
│ ├── MyServiceProvider1.class
│ └── MyServiceProvider2.class
├── local-provider.jar
└── src
└── local
└── provider
├── MyServiceProvider1.java
└── MyServiceProvider2.java
</pre>Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-37555851088433303432020-02-10T12:00:00.000+09:002020-02-10T12:00:02.135+09:00[Java] ジェネリックメソッドの戻り値は境界ワイルドカード型にしない<a href="https://blog.sgnet.co.jp/2020/01/java-concurrenthashmaphashmap.html">前回</a>に続き、Effective Javaネタ。<br>
<h2><span style="color:gray">PECS</span></h2>
Effective Java 項目31 APIの柔軟性向上のために境界ワイルドカードを使う によると"最大限の柔軟性のためには、プロデューサ(生産者)かコンシューマ(消費者)を表す入力パラメータに対してワイルドカード型を使ってください。"とある。<br>
確かにメソッドの引数に関してはその通りだと思う。
しかし、メソッド使用者の利便性を考えると、"戻り値型として境界ワイルドカード型を使わないでください。"とある。こちらについてはどうだろうか。<br>
<a name='more'></a>
<h2><span style="color:gray">戻り値としてリストが返ってくるメソッド</span></h2>
例えば、リストを生成するメソッド(まさにProducer!)List<T> func<T>(T a)を考えよう。
<pre class="prettyprint linenums">
List<Integer> li = func(1);
</pre>
<br>
funcはリストを作成するメソッドなので、戻り値をより一般的にしようとして、List<? super T> func<T>(T a)とすると困ったことが起こる。
<pre class="prettyprint linenums">
// コンパイルエラー!
List<Integer> li = func(1);
</pre>
<h2><span style="color:gray">ジェネリック型同士の汎化・特化関係</span></h2>
...と、ここで、これはPECS原則と代入の方向を勘違いしていることが原因だと気づいた。そもそも、戻り値をどう使うかはメソッド使用者側の問題なので、メソッドのデザインとして、PECSの原則には当てはまらない。<br>
<br>
ワイルドカード型のジェネリック変数にワイルドカードでないジェネリック型を代入することはできるけど、ワイルドカードでないジェネリック型の変数にワイルドカード型を代入することはできない。<br>
<br>
というか、代入先のワイルドカードがより広い範囲を指しており、範囲に矛盾がなければ代入が可能ということになる。
<pre class="prettyprint linenums">
List<Integer> li = new ...;
// ? extends Number は Integer よりも広い.
List<? extends Number> len = li;
// ? は ? extends Number よりも広い.
List<?> lq = len;
List<Number> ln = new ...;
// ? super Integer は Number よりも広い.
List<? super Integer> lsi = ln;
</pre>
<br>
だから、イメージとしてはワイルドカード型は非ワイルドカード型を汎化したもので、非ワイルドカード型はワイルドカード型を特化したものと考えるといいのかもしれない。<br>
<br>
ワイルドカード型同士も、より狭い範囲を指している型ほど特化している。より広い範囲を指している型ほど汎化しているということになる。
<pre class="prettyprint linenums">
List<? extends Number> len = ...;
// ? extends Object は ? extends Number よりも広い.
List<? extends Object> leo = len;
</pre>
Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-77374002973997810232020-01-27T12:00:00.000+09:002020-01-27T12:00:07.377+09:00[Java] ConcurrentHashMapとHashMapの非互換性<h2><span style="color:gray">Effective Javaによると</span></h2>
Effective Java 第3版の項目81 "waitとnotifyよりも並行処理ユーティリティを選ぶ"によるとConcurrentHashMap.putIfAbsentをそのまま使うよりも、
<pre class="prettyprint linenums">
public static String intern(String s) {
String previousValue = map.putIfAbsent(s, s);
return previousValue == null ? s : previousValue;
}
</pre>
<br>
一度ConcurrentHashMap.getで要素の存在を確認してからputIfAbsentを呼び出したほうが、高速化するというテクニックが出てくる。
<a name='more'></a>
<pre class="prettyprint linenums">
public static String intern(String s) {
String result = map.get(s);
if (result == null) {
result = map.putIfAbsent(s, s);
if (result == null)
result = s;
}
return result;
}
</pre>
<h2><span style="color:gray">普通に考えると</span></h2>
項番81は本件が主題なのではなく、少し触れられている程度の話なのだけれど、ConcurrentHashMap.getのみであれば同期操作が発生しないためだろう。<br>
そう考えると、ifの条件が成立する可能性が高い場合は、このテクニックを使うほうが速そうだし、逆の場合はかえって遅くなりそうな気がする。<br>
<br>
完全に定数であれば、要素を追加して、Unmodifiableにした後は、同期する必要もなくgetするだけなので、このテクニックを使う必要はなさそう。<br>
実際には一度計算したものを再計算したくないような場面で、キャッシュとしてConcurrentHashMapを使うときに役立つのかな。<br>
<br>
ただし、マップに要素が存在しても値がnullの場合はifの条件が成立しないため、結局かえって遅くなるパターンになりそう。
こういう使い方はあまりないと思うけど。<br>
<h2><span style="color:gray">ConcurrentHashMapとHashMapの非互換性</span></h2>
...と思ったんだけど、実際にConcurrentHashMapにnullの値を入れようとしたら、NullPointerExceptionが発生してしまった。<br>
ConcurrentHashMapのAPI仕様を確認すると確かに次のように書いてある。<br>
<br>
Hashtableと同様に(HashMapとは異なる)、このクラスは、キーまたは値としてnullが使用されることを許可しません。<br>
<br>
うーん、これだと最初HashMapを使っていて、後からConcurrentHashMapに変えた場合に困ったことになってしまう可能性がある。<br>
キーはともかく値としてもMapにnullを入れるデザインは最初からしないほうがいいってことかな。<br>
Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-23342226895423920192019-01-15T12:00:00.000+09:002019-01-15T12:00:01.213+09:00[Kotlin] Kotlinで末尾再帰最適化 その2<a href="https://blog.sgnet.co.jp/2019/01/kotlin-kotlin-1.html">その1</a>ではtailrecを使って、フィボナッチ数を生成してみたけど、実際最適化されているのだろうか?<br>
<br>
<h2><span style="color:gray">ホントか?</span></h2>
今度は実行用のjarではなく、クラスファイル単体を作成する。<br>
<br>
<pre class="prompt">
$ kotlinc fibonacci.kt
</pre>
とすると、FibonacciKt.classが作成されるので、これをjavapで見てみる。<br>
<pre class="prompt">
$ javap -p -c FibonacciKt.class
</pre>
<pre class="prettyprint linenums">
Compiled from "fibonacci.kt"
public final class FibonacciKt {
(中略)
public static final java.util.List<java.lang.Integer> fibonacci0(int, java.util.List<java.lang.Integer>);
Code:
0: aload_1
1: ldc #61 // String list
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_1
7: invokeinterface #65, 1 // InterfaceMethod java/util/List.size:()I
12: iload_0
13: if_icmplt 20
16: aload_1
17: goto 45
20: aload_1
21: checkcast #67 // class java/util/Collection
24: aload_1
25: iconst_2
26: invokestatic #71 // Method kotlin/collections/CollectionsKt.takeLast:(Ljava/util/List;I)Ljava/util/List;
29: checkcast #73 // class java/lang/Iterable
32: invokestatic #77 // Method kotlin/collections/CollectionsKt.sumOfInt:(Ljava/lang/Iterable;)I
35: invokestatic #42 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
38: invokestatic #81 // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Object;)Ljava/util/List;
41: astore_1
42: goto 0
45: areturn
}
</pre>
<br>
重要そうなところを抜き出すと<br>
<pre class="prompt">
7: invokeinterface #65, 1 // InterfaceMethod java/util/List.size:()I
</pre>
で、list.sizeを呼び出して
<pre class="prompt">
13: if_icmplt 20
</pre>
list.size < n なら 20:へジャンプ。ループが継続する方がこっち。
<pre class="prompt">
17: goto 45
</pre>
20:へジャンプしない場合はここに来て、45:へジャンプ。これで、ループ終了。
<pre class="prompt">
42: goto 0
</pre>
ループ最下部。0:へジャンプして、ループ継続。<br>
<br>
確かに fibonacci0 の再帰呼び出しが存在せず、ループに展開されている。<br>
<br>
<h2><span style="color:gray">tailrecがない場合</span></h2>
今度は fibonacci0 関数から tailrec を取ってクラスファイルを作ってみる。<br>
<br>
fibonacci.kt(一部)
<pre class="prettyprint linenums">
fun fibonacci0(n: Int, list: List<Int>): List<Int>
= if (list.size >= n) list
else fibonacci0(n, list.plus(list.takeLast(2).sum()))
</pre>
<br>
<pre class="prompt">
$ kotlinc fibonacci.kt
$ javap -p -c FibonacciKt.class
</pre>
<pre class="prettyprint linenums">
Compiled from "fibonacci.kt"
public final class FibonacciKt {
(中略)
public static final java.util.List<java.lang.Integer> fibonacci0(int, java.util.List<java.lang.Integer>);
Code:
0: aload_1
1: ldc #61 // String list
3: invokestatic #15 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
6: aload_1
7: invokeinterface #65, 1 // InterfaceMethod java/util/List.size:()I
12: iload_0
13: if_icmplt 20
16: aload_1
17: goto 45
20: iload_0
21: aload_1
22: checkcast #67 // class java/util/Collection
25: aload_1
26: iconst_2
27: invokestatic #71 // Method kotlin/collections/CollectionsKt.takeLast:(Ljava/util/List;I)Ljava/util/List;
30: checkcast #73 // class java/lang/Iterable
33: invokestatic #77 // Method kotlin/collections/CollectionsKt.sumOfInt:(Ljava/lang/Iterable;)I
36: invokestatic #42 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
39: invokestatic #81 // Method kotlin/collections/CollectionsKt.plus:(Ljava/util/Collection;Ljava/lang/Object;)Ljava/util/List;
42: invokestatic #55 // Method fibonacci0:(ILjava/util/List;)Ljava/util/List;
45: areturn
}
</pre>
<br>
変数のロード処理なんかが変わっている箇所もあるけど、一番重要なのは最後のリターンの直前が
<pre class="prompt">
42: goto 0
</pre>
ではなく、fibonacci0の呼び出しに変わっているところ。
<pre class="prompt">
42: invokestatic #55 // Method fibonacci0:(ILjava/util/List;)Ljava/util/List;
</pre>
<br>
確かに tailrec を付けていない場合は、通常の再帰処理になっている。Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-14362328222466171062019-01-07T12:00:00.000+09:002019-01-12T15:13:52.723+09:00[Kotlin] Kotlinで末尾再帰最適化 その1Kotlinでは関数にtailrec宣言を付けることによって、末尾再帰最適化がされる(一部制限があるようですが)!とのことなので、早速やってみます。<br>
何はともあれ、フィボナッチ数。<br>
<a name='more'></a>
<h2><span style="color:gray">末尾再帰でフィボナッチ数</span></h2>
fibonacci.kt
<pre class="prettyprint linenums">
fun main(args: Array<String>) {
println(fibonacci(Integer.parseInt(args[0])))
}
fun fibonacci(n: Int): List<Int>
= if (n <= 1) listOf(1)
else fibonacci0(n, listOf(1, 1))
tailrec fun fibonacci0(n: Int, list: List<Int>): List<Int>
= if (list.size >= n) list
else fibonacci0(n, list.plus(list.takeLast(2).sum()))
</pre>
<br>
<pre class="prompt">
$ kotlinc -version
info: kotlinc-jvm 1.3.11 (JRE 1.8.0_92-b14)
$ kotlinc fibonacci.kt -include-runtime -d fibonacci.jar
$ java -jar fibonacci.jar 5
[1, 1, 2, 3, 5]
</pre>
<br>
とりあえず、それっぽく動作していることは確認できた。<a href="https://blog.sgnet.co.jp/2019/01/kotlin-kotlin-2.html">その2</a>に続く〜Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-58766634615039079122019-01-01T00:00:00.000+09:002019-01-01T00:00:38.078+09:00[JavaScript] 並列処理と再帰呼び出しで関数受け取り明けましておめでとうございます。2019年になってしまいましたね。<br>
さて、<a href="https://blog.sgnet.co.jp/2018/12/javascript.html">[JavaScript] 並列処理と再帰呼び出し</a>で関数を受け取るようにすれば、汎用的にできるんじゃっ、ということだったので、やってみました。<br>
<br>
<a name='more'></a>
<pre class="prettyprint linenums">
function parallel_for(body, pred, args) {
if (!pred(args)) return;
args = body(args);
Promise.resolve().then(() => parallel_for(body, pred, args));
}
b = (i) => { console.log(i); return ++i; };
p = (i) => i < 100;
parallel_for(b, p, 0);
parallel_for(b, p, 0);
</pre>
<br>
ま、並列処理やりたかったら WebWorker 使えって話ですが。
Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0tag:blogger.com,1999:blog-382524739729445649.post-73971270629454712182018-12-25T12:00:00.000+09:002018-12-25T12:00:13.558+09:00[Java] enumとDBに格納するコードを対応付けたい<h2><span style="color:gray">enum拡張</span></h2>
Javaのenumを拡張して、何らかの値を持たせることはよくあると思う。<br>
特にDBに格納するコードとenumを対応付けたくなる。<br>
<br>
そういう場合は、コンストラクタに引数を持たせて初期化する。<br>
<br>
Fruits.java<br>
<pre class="prettyprint linenums">
enum Fruits implements CodeEnum {
Apple(1),
Orange(2),
None(99);
private int code;
private Fruits(int code) { this.code = code; }
}
</pre>
<a name='more'></a>
<br>
<h2><span style="color:gray">コード格納用インターフェース</span></h2>
コードとenumを変換する手順はインターフェースに持たせておいて、具体的なenum型ではこれを利用することにする。<br>
<br>
CodeEnum.java<br>
<pre class="prettyprint linenums">
interface CodeEnum {
int getCode();
public static <T extends CodeEnum> T valueOf(T[] values, int code) {
for (T v : values) {
if (v.getCode() == code) return v;
}
throw new IllegalArgumentException("No enum constant " + code);
}
}
</pre>
Fruits.java<br>
<pre class="prettyprint linenums">
enum Fruits implements CodeEnum {
Apple(1),
Orange(2),
None(99);
private Fruits(int code) { this.code = code; }
private int code;
@Override public int getCode() { return code; }
public static Fruits valueOf(int code) {
return CodeEnum.valueOf(Fruits.values(), code);
}
}
</pre>
<pre class="prettyprint linenums">
Fruits.Apple.getCode();
// 1
Fruits.None.getCode();
// 99
Fruits.valueOf(2);
// Orange
Fruits.valueOf(99);
// None
</pre>
<br>
こうしておけば、特定のORマッピングツールなどに依存せずに、enumの定義だけでJavaのコードと外部コード定義の橋渡しができる。
Ichioka Takehikohttp://www.blogger.com/profile/13065122724529180876noreply@blogger.com0