HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

Go言語のパッケージ名と変数名の罠

Go言語で以下のようなコードを書いて一瞬ハマったのでメモ。

package main

import (
    "fmt"
    "container/list"
)

func main() {
    list := list.New()
    list.PushBack(1)
    list.PushBack(2)
    list.PushBack(3)
    for elem := list.Front(); elem != nil; elem = elem.Next() {
        fmt.Println(elem.Value)
    }

    list2 := list.New()
    list2.PushBackList(list)
    for elem := list2.Front(); elem != nil; elem = elem.Next() {
        fmt.Println(elem.Value)
    }
}
$ go run Main.go
# command-line-arguments
./Main.go:17: list.New undefined (type *list.List has no field or method New)
$ 

‥えっ?

17行目は以下の部分。

    list2 := list.New()

実はこれ、9行目の

    list := list.New()

listという名前の変数を定義することによって、パッケージ名よりも変数名の方が優先されてしまうことが原因(多分)。

なので、以下のようにでも書いてやり、パッケージ名と変数名の衝突を避けてやらないといけない。

package main

import (
    "fmt"
    "container/list"
)

func main() {
    aList := list.New()
    aList.PushBack(1)
    aList.PushBack(2)
    aList.PushBack(3)
    for elem := aList.Front(); elem != nil; elem = elem.Next() {
        fmt.Println(elem.Value)
    }

    aList2 := list.New()
    aList2.PushBackList(aList)
    for elem := aList2.Front(); elem != nil; elem = elem.Next() {
        fmt.Println(elem.Value)
    }
}

Javaだったら、以下のようなコードが許されるんだがな(良い悪いは別として)。

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Integer> List = new ArrayList<>();
        List.add(1);
        List.add(2);
        List.add(3);
        for (Integer elem : List) {
            System.out.println(elem);
        }

        List<Integer> List2 = new ArrayList<>();
        List2.addAll(List);
        for (Integer elem : List2) {
            System.out.println(elem);
        }
    }
}

標準入力から数値列を読み込んで、逆順に標準出力に吐き出す

手元にある各言語で、標準入力から数値列を読み込んで、逆順にしたうえで標準出力に吐き出すプログラムを書いてみようと思ったメモ。

標準入力から入力される数値列の要件は以下の通り。

  • 1行に1つの数値が書かれている
    • 不正入力のチェックは不要とする
  • いくつの数値が入力されるかは不明(ただし、メモリがあふれることが無い程度に手加減する)
  • 入力される数値は符号付32ビット整数とする

入力される数値の上限数設定が無いことで、前々回、前回のように配列を静的に準備しておくということができないので、真面目に動的な配列あるいはリスト構造を考えないといけなくなるところがポイント。

環境

手元にあるものということで、環境は以下のものに限定する。

入力ファイルの例

  • 001.txt
0
1
-1
256
-256
32768
-32768
2147483647
-2147483648

期待される出力の例

  • 001.txt
-2147483648
2147483647
-32768
32768
-256
256
-1
1
0

Java

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            List<Integer> list = new ArrayList<>();
            while (in.hasNextInt()) {
                list.add(in.nextInt());
            }
            Collections.reverse(list);
            for (Integer num : list) {
                out.println(num);
            }
        }
    }
}

Javaの場合、Listの要素を逆順にするメソッドがCollectionsクラスに用意されているので、それを使えば一発。

C

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

struct _IntNode {
    int value;
    struct _IntNode* prev;
    struct _IntNode* next;
};
typedef struct _IntNode IntNode;
typedef struct {
    int size;
    IntNode* first;
    IntNode* last;
} IntList;

IntList* IntList_new() {
    IntList* list = (IntList*)calloc(sizeof(IntList), 1);
    list->first = NULL;
    list->last = NULL;
    list->size = 0;
    return list;
}
void IntList_destroy(IntList* list) {
    IntNode* node = list->first;
    while (node != NULL) {
        IntNode* next = node->next;
        free(node);
        node = next;
    }
    free(list);
}
void IntList_add(IntList* list, int value) {
    IntNode* node = (IntNode*)calloc(sizeof(IntNode), 1);
    node->next = NULL;
    if (list->last != NULL) {
        list->last->next = node;
        node->prev = list->last;
    } else {
        list->first = node;
        node->prev = NULL;
    }
    list->size++;
    list->last = node;
    node->value = value;
}
void IntList_reverse(IntList* list) {
    int left = 0;
    int right = list->size - 1;
    IntNode* leftNode = list->first;
    IntNode* rightNode = list->last;
    while (left < right) {
        int tmp = leftNode->value;
        leftNode->value = rightNode->value;
        rightNode->value = tmp;

        ++left;
        --right;
        leftNode = leftNode->next;
        rightNode = rightNode->prev;
    }
}

int main(int argc, char** argv) {
    IntList* list = IntList_new();

    int num;
    while (scanf("%ld", &num) == 1) {
        IntList_add(list, num);
    }
    IntList_reverse(list);
    for (IntNode* node = list->first; node != NULL; node = node->next) {
        printf("%d\n", node->value);
    }

    IntList_destroy(list);

    return 0;
}

C言語にはリスト構造がそもそも無いので、そこの実装から。今回はlinked listで実装したが、Javaで言うところのArrayList方式の実装も考えられる。今回は面倒だったので止めたが。

逆順にする処理は、実装したlinked listに対して愚直に各要素の値を入れ替える処理。

C++

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

void reverse_vector(vector<int>& list) {
    for (int left = 0, right = list.size() - 1; left < right; ++left, --right) {
        int tmp = list[left];
        list[left] = list[right];
        list[right] = tmp;
    }
}

int main(int argc, char** argv) {
    vector<int> list;
    int num;

    while (cin >> num) {
        list.push_back(num);
    }

    reverse_vector(list);

    for (int i = 0; i < list.size(); ++i) {
        cout << list[i] << endl;
    }

    return EXIT_SUCCESS;
}

逆順にする処理は、愚直に各要素の値を入れ替える処理。

PHP

<?php

$lines = file('php://stdin');
$ary = array();
foreach ($lines as $line) {
    $ary[] = (int)$line;
}

$left = 0;
$right = count($ary) - 1;
while ($left < $right) {
    $tmp = $ary[$left];
    $ary[$left] = $ary[$right];
    $ary[$right] = $tmp;

    ++$left;
    --$right;
}

foreach ($ary as $num) {
    printf("%d\n", $num);
}

逆順にする処理は、愚直に各要素の値を入れ替える処理。

Python 2

import sys

list = []
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    list.append(int(line))

list.reverse()

for num in list:
    print num

逆順にする処理は、reverseメソッドで一発。

Python 3

import sys

list = []
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    list.append(int(line))

list.reverse()

for num in list:
    print(num)

逆順にする処理は、reverseメソッドで一発。

Ruby

list = []
while line = STDIN.gets
    num = line.to_i
    list.push(num)
end

list = list.reverse()

for num in list
    print num,"\n"
end

逆順にする処理は、reverseメソッドで一発。

Perl

my $line;
my @list = ();
my $count = 0;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    $list[$count] = $num;
    ++$count;
}

@list = reverse @list;

for (my $i = 0; $i < $count; ++$i) {
    print "$list[$i]\n";
}

逆順にする処理は、reverse関数で一発。

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "container/list"
)

func main() {
    l := list.New()
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s := string(buf)
        num, err2 := strconv.Atoi(s)
        if err2 != nil {
            panic(err2)
        }
        l.PushBack(num)
        buf = make([]byte, 0, 1024)
    }

    l2 := list.New()
    for elem := l.Back(); elem != nil; elem = elem.Prev() {
        l2.PushBack(elem.Value)
    }
    l = l2

    for elem := l.Front(); elem != nil; elem = elem.Next() {
        fmt.Println(elem.Value)
    }
}

初めてlistコンテナを使ってみた。

逆順にする処理は、元のリストを末尾から捜査して別のリストに放り込むことで実現。 そもそも、入力処理の際にlist.PushFront()すればいいという話もあるが、それはそれとして(ぉ。

bash

#! /bin/bash

nl -ba | sort -nr | cut -f 2

nlコマンドで行番号を付けて、行番号で逆順ソートして、行番号をカットする、という単純な処理。

標準入力から数値列を読み込んで、降順にソートして標準出力に吐き出す

手元にある各言語で、標準入力から数値列を読み込んで、降順にソートしたうえで標準出力に吐き出すプログラムを書いてみようと思ったメモ。 プログラム言語にもよるが、昇順は簡単なのに降順となったとたんに苦労するケースがあったりする。

標準入力から入力される数値列の要件は以下の通り。

  • 1行に1つの数値が書かれている
    • 不正入力のチェックは不要とする
  • 最大で256個の数値が入力される
  • 入力される数値は符号付32ビット整数とする

環境

手元にあるものということで、環境は以下のものに限定する。

入力ファイルの例

  • 001.txt
0
1
-1
256
-256
32768
-32768
2147483647
-2147483648

期待される出力の例

  • 001.txt
2147483647
32768
256
1
0
-1
-256
-32768
-2147483648

Java

入力される数値の上限数が分かっているということで、Javaでは2パターン作ってみた。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf;
            int count = 0;
            int[] ary = new int[256];
            while ((buf = in.readLine()) != null) {
                ary[count] = Integer.parseInt(buf);
                ++count;
            }
            Arrays.sort(ary, 0, count);
            // 降順ソートする手段がないので、昇順ソートした後に逆順にする。。
            for (int left = 0, right = count - 1; left < right; ++left, --right) {
                int tmp = ary[left];
                ary[left] = ary[right];
                ary[right] = tmp;
            }
            for (int i = 0; i < count; ++i) {
                out.println(ary[i]);
            }
        } catch (NumberFormatException e) {
            // 今回は不正入力のチェックは不要なので、RuntimeExceptionを投げておく。
            throw new RuntimeException(e);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

昇順との違いは‥コメントにある通り「降順ソートする手段がないので、昇順ソートした後に逆順にする」という手法を取っていること。

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            List<Integer> list = new ArrayList<>();
            while (in.hasNextInt()) {
                list.add(in.nextInt());
            }
            Collections.sort(list, Collections.reverseOrder());
            for (Integer num : list) {
                out.println(num);
            }
        }
    }
}

昇順との違いは、Collections.sortの第2パラメータに逆順を表すComparatorを指定していること。

C

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

int cmp(const void* pa, const void* pb) {
    int a = *(const int*)pa;
    int b = *(const int*)pb;
    if (a < b) {
        return 1;
    } else if (a > b) {
        return -1;
    } else {
        return 0;
    }
}

int main(int argc, char** argv) {
    int ary[256];
    int count = 0;
    while (scanf("%ld", &ary[count]) == 1) {
        ++count;
    }
    qsort(ary, count, sizeof(int), cmp);
    for (int i = 0; i < count; ++i) {
        printf("%d\n", ary[i]);
    }

    return 0;
}

昇順との違いは、cmp関数の大小による戻り値が逆になっていること。

C++

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

bool cmp(int a, int b) {
    return a > b;
}

int main(int argc, char** argv) {
    vector<int> list;
    int num;
    int count = 0;

    while (cin >> num) {
        list.push_back(num);
        ++count;
    }
    sort(list.begin(), list.end(), cmp);
    for (int i = 0; i < list.size(); ++i) {
        cout << list[i] << endl;
    }

    return EXIT_SUCCESS;
}

昇順との違いは、sort関数の第3パラメータに比較関数を指定して、逆順になるように関数を定義していること。

PHP

<?php

$lines = file('php://stdin');
$ary = array();
foreach ($lines as $line) {
    $ary[] = (int)$line;
}
rsort($ary);
foreach ($ary as $num) {
    printf("%d\n", $num);
}

昇順がsortに対して、降順がrsortという、ただそれだけ。

Python 2

import sys

list = []
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    list.append(int(line))

list.sort(reverse=True)

for num in list:
    print num

昇順との違いは、reverseパラメータにTrueを指定していること。

Python 3

import sys

list = []
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    list.append(int(line))

list.sort(reverse=True)

for num in list:
    print(num)

昇順との違いは、reverseパラメータにTrueを指定していること。

Ruby

list = []
while line = STDIN.gets
    num = line.to_i
    list.push(num)
end
list = list.sort() {|a, b| b <=> a}
for num in list
    print num,"\n"
end

昇順との違いは、逆順になるように何か書いているが、正直よく分かってない(待て)。

Perl

my $line;
my @list = ();
my $count = 0;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    $list[$count] = $num;
    ++$count;
}
@list = sort {$b <=> $a} @list;
for (my $i = 0; $i < $count; ++$i) {
    print "$list[$i]\n";
}

昇順との違いは、$a$bが逆になっていること。これで逆順(降順)ソートになるのだから、つくづくPerlは不思議な言語‥

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "sort"
)

func main() {
    tmp := make([]int, 256)
    count := 0
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s := string(buf)
        num, err2 := strconv.Atoi(s)
        if err2 != nil {
            panic(err2)
        }
        tmp[count] = num
        count++
        buf = make([]byte, 0, 1024)
    }
    ary := make([]int, count)
    for i := 0; i < count; i++ {
        ary[i] = tmp[i]
    }
    sort.Sort(sort.Reverse(sort.IntSlice(ary)))
    for i := 0; i < count; i++ {
        fmt.Println(ary[i])
    }
}

これが最適なコードなのかどうかよく分からん‥

昇順との違いは、sort.Intsがなにやらややこしいことになっていること。

bash

#! /bin/bash

sort -nr

昇順との違いは、いわずもがな、-r(everse)の指定。

標準入力から数値列を読み込んで、昇順にソートして標準出力に吐き出す

手元にある各言語で、標準入力から数値列を読み込んで、昇順にソートしたうえで標準出力に吐き出すプログラムを書いてみようと思ったメモ。

標準入力から入力される数値列の要件は以下の通り。

  • 1行に1つの数値が書かれている
    • 不正入力のチェックは不要とする
  • 最大で256個の数値が入力される
  • 入力される数値は符号付32ビット整数とする

環境

手元にあるものということで、環境は以下のものに限定する。

入力ファイルの例

  • 001.txt
0
1
-1
256
-256
32768
-32768
2147483647
-2147483648

期待される出力の例

  • 001.txt
-2147483648
-32768
-256
-1
0
1
256
32768
2147483647

Java

入力される数値の上限数が分かっているということで、Javaでは2パターン作ってみた。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf;
            int count = 0;
            int[] ary = new int[256];
            while ((buf = in.readLine()) != null) {
                ary[count] = Integer.parseInt(buf);
                ++count;
            }
            Arrays.sort(ary, 0, count);
            for (int i = 0; i < count; ++i) {
                out.println(ary[i]);
            }
        } catch (NumberFormatException e) {
            // 今回は不正入力のチェックは不要なので、RuntimeExceptionを投げておく。
            throw new RuntimeException(e);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            List<Integer> list = new ArrayList<>();
            while (in.hasNextInt()) {
                list.add(in.nextInt());
            }
            Collections.sort(list);
            for (Integer num : list) {
                out.println(num);
            }
        }
    }
}

C

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

int cmp(const void* pa, const void* pb) {
    int a = *(const int*)pa;
    int b = *(const int*)pb;
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}

int main(int argc, char** argv) {
    int ary[256];
    int count = 0;
    while (scanf("%ld", &ary[count]) == 1) {
        ++count;
    }
    qsort(ary, count, sizeof(int), cmp);
    for (int i = 0; i < count; ++i) {
        printf("%d\n", ary[i]);
    }

    return 0;
}

C++

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char** argv) {
    vector<int> list;
    int num;
    int count = 0;

    while (cin >> num) {
        list.push_back(num);
        ++count;
    }
    sort(list.begin(), list.end());
    for (int i = 0; i < list.size(); ++i) {
        cout << list[i] << endl;
    }

    return EXIT_SUCCESS;
}

PHP

<?php

$lines = file('php://stdin');
$ary = array();
foreach ($lines as $line) {
    $ary[] = (int)$line;
}
sort($ary);
foreach ($ary as $num) {
    printf("%d\n", $num);
}

Python 2

import sys

list = []
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    list.append(int(line))

list.sort()

for num in list:
    print num

Python 3

import sys

list = []
while True:
    line = sys.stdin.readline()
    if line == '':
        break
    list.append(int(line))

list.sort()

for num in list:
    print(num)

Ruby

list = []
while line = STDIN.gets
    num = line.to_i
    list.push(num)
end
list = list.sort()
for num in list
    print num,"\n"
end

Perl

my $line;
my @list = ();
my $count = 0;
while ($line = readline(STDIN)) {
    my $num = $line + 0;
    $list[$count] = $num;
    ++$count;
}
@list = sort {$a <=> $b} @list;
for (my $i = 0; $i < $count; ++$i) {
    print "$list[$i]\n";
}

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "sort"
)

func main() {
    tmp := make([]int, 256)
    count := 0
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        s := string(buf)
        num, err2 := strconv.Atoi(s)
        if err2 != nil {
            panic(err2)
        }
        tmp[count] = num
        count++
        buf = make([]byte, 0, 1024)
    }
    ary := make([]int, count)
    for i := 0; i < count; i++ {
        ary[i] = tmp[i]
    }
    sort.Ints(ary)
    for i := 0; i < count; i++ {
        fmt.Println(ary[i])
    }
}

これが最適なコードなのかどうかよく分からん‥

bash

#! /bin/bash

sort -n

入力文字列をparseIntで解析する場合とScannerを使用する場合との速度の違い

プログラミングコンテストとかでjava.util.Scannerを使って入力の数値を取得しようとすると、どうしてもInteger.parseIntした場合より実行速度が遅くなってしまうということで、どの程度のものなのかを簡単に調べてみたメモ。

検証環境

CentOS 7のVM(VirtualBox on Windows 10 on Let's Note CF-SX2)上で、OpenJDK 1.8.0_131を使用して測定。

ソースコード

parseInt版

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
    public static void main(String[] args) {
        long S = System.currentTimeMillis();

        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf;
            while ((buf = in.readLine()) != null) {
                String[] tokens = buf.split(" ");
                for (String token : tokens) {
                    int num = Integer.parseInt(token);
                    System.out.println(num);
                }
            }
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }

        long G = System.currentTimeMillis();
        System.err.println((G - S) + "ms");
    }
}

Scanner版

import java.io.PrintWriter;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        long S = System.currentTimeMillis();

        Scanner sc = new Scanner(System.in);
        PrintWriter out = new PrintWriter(System.out);
        while (sc.hasNextInt()) {
            int num = sc.nextInt();
            out.println(num);
        }
        out.flush();

        long G = System.currentTimeMillis();
        System.err.println((G - S) + "ms");
    }
}

入力データ

パターン1

あまり入力数が多くないところで、1行に1数値の場合と1行に5数値の場合のデータを用意して比較。例えば、1行に5数値で1,000行の場合のデータは以下のような感じになる。

1 1 1 1 1
2 2 2 2 2
3 3 3 3 3
4 4 4 4 4
5 5 5 5 5
:
:
996 996 996 996 996
997 997 997 997 997
998 998 998 998 998
999 999 999 999 999
1000 1000 1000 1000 1000

パターン2

では、本当にScannerの方がどんなケースでも遅いのか、と思って以下のパターンを試してみた。

N=10,000、100,000~1,000,000の11ケースで以下のような入力を食わせてみた。

1
2
3
4
5
:
:
N-4
N-3
N-2
N-1
N

測定結果

パターン1

こちらは予想通りというか、Scanner版の方が実行速度がかかっている。

N parseInt(1) Scanner(1) parseInt(5) Scanner(5)
0 0 36
1000 40 98 82 131
2000 51 121 130 175
3000 65 126 160 191
4000 69 130 183 246
5000 76 141 191 242
6000 79 153 210 267
7000 83 150 229 289
8000 108 163 257 323
9000 113 169 292 330

f:id:hhelibex:20171027220416p:plain

パターン2

N=200,000から300,000の辺りで速度が逆転している。大量の入力に対しては、なぜかparseInt版の方が速度的には不利なようだ。

N parseInt Scanner
10000 117 172
100000 452 471
200000 619 690
300000 804 761
400000 957 862
500000 1157 1060
600000 1276 1122
700000 1512 1105
800000 1692 1188
900000 1891 1321
1000000 1976 1412

f:id:hhelibex:20171027220508p:plain

JDKのバージョンによる文字列連結処理の速度の違い

過去にもなんか調べた気がしないでもないが、Java 9リリース記念ということで、Sun/Oracle JDK限定だが、JDKのバージョンを変えたときの「+」連結/StringBuffer/StringBuilderの速度の違いをざっと調べてみた。

平均値とか出すのが面倒だったので、測定は一発勝負(ぉ‥

測定環境

CentOS 6のVM(VirtualBox on Windows 10 on Let's Note CF-SX2)上で、以下のJava VMを使って測定(java -versionの出力)。

java version "1.4.2_19"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.2_19-b04)
Java HotSpot(TM) Client VM (build 1.4.2_19-b04, mixed mode)
java version "1.5.0_22"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_22-b03)
Java HotSpot(TM) Client VM (build 1.5.0_22-b03, mixed mode, sharing)
java version "1.6.0_45"
Java(TM) SE Runtime Environment (build 1.6.0_45-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)
java version "1.7.0_72"
Java(TM) SE Runtime Environment (build 1.7.0_72-b14)
Java HotSpot(TM) 64-Bit Server VM (build 24.72-b04, mixed mode)
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
java version "9"
Java(TM) SE Runtime Environment (build 9+181)
Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)

ソースコードはこの後に載せるが、コンパイルは以下のような感じで行った。(Xxxの部分には「Plus」「StringBuffer」「StringBuilder」が入る)

ソースコード

MainNnPlus (Nnの部分には「14」「15」が入る)

public class MainNnPlus {
    public static void main(String[] args) {
        int n = args.length > 0 ? Integer.parseInt(args[0]) : 10000;

        long S = System.currentTimeMillis();

        String s = "";
        for (int i = 0; i < n; ++i) {
            s += "a";
        }

        long G = System.currentTimeMillis();
        System.out.println(System.getProperty("java.version") + ":" + n + ":" + (G - S));
    }
}

MainNnStringBuffer (Nnの部分には「14」「15」が入る)

public class MainNnStringBuffer {
    public static void main(String[] args) {
        int n = args.length > 0 ? Integer.parseInt(args[0]) : 10000;

        long S = System.currentTimeMillis();

        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < n; ++i) {
            sb.append("a");
        }

        long G = System.currentTimeMillis();
        System.out.println(System.getProperty("java.version") + ":" + n + ":" + (G - S));
    }
}

Main15StringBuilder

public class Main15StringBuilder {
    public static void main(String[] args) {
        int n = args.length > 0 ? Integer.parseInt(args[0]) : 10000;

        long S = System.currentTimeMillis();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; ++i) {
            sb.append("a");
        }

        long G = System.currentTimeMillis();
        System.out.println(System.getProperty("java.version") + ":" + n + ":" + (G - S));
    }
}

実行(測定)

実行(測定)は以下のようなシェルスクリプトを組んで行った。

#! /bin/bash

d1=3000
max1=30000
d2=10000000
max2=100000000

~/jdk/j2sdk1.4.2_19/bin/javac Main14*.java
~/jdk/jdk1.5.0_22/bin/javac Main15*.java

for c in Main{14,15}Plus ; do
    echo === ${c}
    for ((n = d1; n <= max1; n += d1)); do
        ~/jdk/run.sh -Xmx1024m ${c} ${n}
    done
done
for c in Main{14,15}StringBuffer ; do
    echo === ${c}
    for ((n = d2; n <= max2; n += d2)); do
        ~/jdk/run.sh -Xmx1024m ${c} ${n}
    done
done
for c in Main15StringBuilder ; do
    echo === ${c}
    for ((n = d2; n <= max2; n += d2)); do
        ~/jdk/run.sh -Xmx1024m ${c} ${n}
    done
done

~/jdk/run.shの中身は以下。

#! /bin/bash

d=$(dirname $0)

for jdk in j2sdk1.4.2_19 jdk1.5.0_22 jdk1.6.0_45 jdk1.7.0_72 jdk1.8.0_66 jdk-9 ; do
    if [ -d ${d}/${jdk} ]; then
        ${d}/${jdk}/bin/java $@
    fi
done

測定結果

Main14Plus

1.4.2_19 1.5.0_22 1.6.0_45 1.7.0_72 1.8.0_66 9
3000 11 19 16 22 15 17
6000 28 47 63 72 50 36
9000 57 99 94 103 82 66
12000 113 190 150 154 131 89
15000 216 289 235 213 177 116
18000 357 459 292 225 244 169
21000 538 742 374 264 331 192
24000 793 1064 485 291 402 256
27000 1104 1538 593 319 517 296
30000 1469 2042 726 356 596 352

f:id:hhelibex:20171024225451p:plain

Main15Plus

1.4.2_19 1.5.0_22 1.6.0_45 1.7.0_72 1.8.0_66 9
3000 15 17 22 17 16
6000 47 57 70 49 34
9000 103 90 103 78 56
12000 178 144 152 120 85
15000 291 218 196 173 111
18000 476 292 227 253 150
21000 743 380 255 307 182
24000 1075 494 282 394 232
27000 1552 613 321 495 291
30000 2092 751 367 626 353

f:id:hhelibex:20171024225701p:plain

Main14StringBuffer

1.4.2_19 1.5.0_22 1.6.0_45 1.7.0_72 1.8.0_66 9
10000000 389 269 211 208 191 142
20000000 771 519 397 422 357 241
30000000 1125 809 542 583 500 339
40000000 1541 1068 756 828 716 471
50000000 1993 1310 939 1009 855 584
60000000 2264 1564 1412 1291 1053 839
70000000 2704 1783 1272 1379 1142 760
80000000 3185 2246 1789 1909 1703 1034
90000000 3982 3027 1837 2290 1824 1070
100000000 4518 3455 2542 2108 1665 1120

f:id:hhelibex:20171024225800p:plain

Main15StringBuffer

1.4.2_19 1.5.0_22 1.6.0_45 1.7.0_72 1.8.0_66 9
10000000 369 331 330 272 169
20000000 544 409 608 523 362
30000000 778 659 649 487 444
40000000 1601 751 1173 969 444
50000000 1305 953 1101 1219 762
60000000 2030 1068 1223 1413 920
70000000 2207 1359 1570 1133 707
80000000 2772 1821 2046 1670 889
90000000 2589 2355 2107 1924 1414
100000000 3042 2324 2224 2274 1180

f:id:hhelibex:20171024225915p:plain

Main15StringBuilder

1.4.2_19 1.5.0_22 1.6.0_45 1.7.0_72 1.8.0_66 9
10000000 349 213 203 168 63
20000000 440 286 331 318 157
30000000 808 324 315 298 131
40000000 906 543 655 617 242
50000000 1488 817 778 713 252
60000000 1388 639 636 687 328
70000000 2010 1032 804 644 266
80000000 1782 1309 1183 1047 507
90000000 2530 1117 996 951 387
100000000 2694 1389 1421 1413 567

f:id:hhelibex:20171024230039p:plain

C++の標準入出力についてのメモ

プログラミングコンテストなどでよく見かける以下のコード断片を「おまじない」で片づけるのが嫌だったので調べてみたメモ。

ios_base::sync_with_stdio(false);
cin.tie(NULL);

答えは以下のサイトで解説されているのだが‥

試してみないと気が済まなかったので試してみた。

ios_base::sync_with_stdio(false)の検証

以下のようなコードを実行してみる。

#include <cstdio>
#include <iostream>

using namespace std;

void test1(int n, bool sync = false) {
    for (int i = 0; i < n; ++i) {
        cout << 'a';
        if (sync) {
            flush(cout);
        }
        printf("A");
        if (sync) {
            fflush(stdout);
        }
    }

    fflush(stdout);
    cout << endl;
}

int main(int argc, char* argv[]) {
    int n = 10;

    test1(n);

    ios_base::sync_with_stdio(false);
    test1(n);

    test1(n, true);
}

これを実行すると、以下のような出力が得られる。

aAaAaAaAaAaAaAaAaAaA
AAAAAAAAAAaaaaaaaaaa
aAaAaAaAaAaAaAaAaAaA
  • 1行目の出力は、coutstdoutが同期されている状態なので、flushとかfflushをしなくても、それぞれに書き込んだ文字が交互に出力されることの確認。
  • 2行目の出力は、coutstdoutの同期を切っているので、末尾でfflushを先にしているstdoutへの出力が先にまとめて出てきて、その後にcoutへの出力がまとめて出てくることの確認。
  • 3行目の出力は、手動で同期をしているので、1行目と同じ出力になることの確認。

上記サイトの回答にあるように、これの副作用として標準出力への出力のパフォーマンスが上がるというわけか。

cin.tie(NULL)の検証

以下のようなコードを実行してみる。

#include <iostream>

using namespace std;

void test2(bool tie = false) {
    string name;

    cout << "Enter name:";
    if (tie) {
        flush(cout);
    }
    cin >> name;
    cout << name << endl;
}

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

    cin.tie(NULL);
    test2();

    ios_base::sync_with_stdio(false);
    test2();

    test2(true);
}

これを実行し、4回の入力が求められるので、"A"、"B"、"C"、"D"を順にコンソールから入力した結果が以下。

Enter name:A
A
Enter name:B
B
C
Enter name:C
Enter name:D
D
  • 1回目は、cincoutが結び付けられた状態なので、cinで読み込む前にcoutに書き込んだプロンプト("Enter name:")が画面に出てくることの確認。
  • 2回目は、cincoutの結びつきを切ってみたのだが、1回目と出力が変わらなかった。
  • 3回目は、ならば、と先に試したios_base::sync_with_stdio(false)をやってみたらどうだろうと試した結果、今度は期待通りにcoutに書き込んだプロンプトが即座には出てこなかった。
  • 4回目は、手動でflushしているので、1回目と同じ結果になることの確認。

cin.tie(NULL)だけでは動作上の変化が無くて、ios_base::sync_with_stdio(false)も合わせてしてやらないといけないらしい。cinから読み込むときに、stdioとの同期が有効になっているとstdinとも結びついていることになるから、同期しようとして2回目のケースでプロンプトが先に出てくるということか。