20200711のJavaに関する記事は9件です。

QuarkusがJava Lambdaを救う!?

すごいですQuarkus!!!!!!
JavaのLambdaのコールドスタートがチョッパヤです!!

以下の公式サイトを参考に試しました。

https://quarkus.io/guides/amazon-lambda#tracing-with-aws-xray-and-graalvm

テスト対象

前回のAWS LambdaのJavaは遅い?とほぼ同じですが、以下が異なります。いずれもQuarkusの制限?制約?です。

  • アノテーションが付いてる
  • httpClientがUrlConnectionHttpClient

ソース全体
package example;

import javax.inject.Named;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;

import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;

@Named("test")
public class TestLambda implements RequestHandler<Object, Object> {

    @Override
    public Object handleRequest(Object input, Context context) {

        String ENV_BUCKET = System.getenv("BUCKET");

        S3Client s3 = S3Client.builder()
                            .region(Region.AP_NORTHEAST_1)
                            .httpClient(software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient.builder().build())
                            .build();

        PutObjectResponse result = s3.putObject(
                PutObjectRequest.builder().bucket(ENV_BUCKET).key("filename.txt").build(),
                RequestBody.fromString("contents"));

        System.out.println(result);

        return "";
    }
}

検証結果

回数 レイテンシ(ms) 処理内容
1 2700
2 250
3 305
4 319
5 187

前回はコールドスタートが6200msだったので、めちゃ早になりました!

検証結果(+Provisioned Concurrency)

こんなチョッパヤなQuarkusさんをProvisionedにしたらどうなるんでしょうね。ワクワクしますね。

回数 レイテンシ(ms) 処理内容
1 417
2 198
3 206
4 270
5 147

期待を裏切らない速さ!すごいよ、Quarkus!

参考

https://quarkus.io/guides/amazon-lambda#tracing-with-aws-xray-and-graalvm
https://aws.amazon.com/jp/blogs/architecture/field-notes-optimize-your-java-application-for-aws-lambda-with-quarkus/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Java】Spigot プラグイン開発 - コマンドをバニラコマンドとして登録する

概要

・普通はplugin.ymlで登録するコマンドをバニラコマンドとして登録する。

対象読者

・プラグイン開発の環境が整っている。

環境

・Windows 10 - 1909
・Eclipse IDE Version: 2020-06 (4.16.0)
・Minecraft 1.16.1
・Spigot 1.16.1

注意

ここでは、Minecraft 1.13から追加されたコマンドエンジン brigadier を使用するので、1.13より下のバージョンではできないかもしれません。

作成するコマンド

・今回は、無難に「プレイヤーを飛べるようにする」というコマンドを作成します。
・コマンドの構文
/fly <プレイヤー> allow

ソースコードと説明

・まずは、クラスを作成しましょう。

FlyCommand.java
package com.hamusuke.qiita.command;

import java.util.Collection;

import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;

import net.minecraft.server.v1_16_R1.ArgumentEntity;
import net.minecraft.server.v1_16_R1.ChatMessage;
import net.minecraft.server.v1_16_R1.CommandListenerWrapper;
import net.minecraft.server.v1_16_R1.EntityPlayer;

public class FlyCommand {
    public static void register(CommandDispatcher<CommandListenerWrapper> dispatcher) {
        LiteralArgumentBuilder<CommandListenerWrapper> literalArgumentBuilder = net.minecraft.server.v1_16_R1.CommandDispatcher.a("fly").requires((permission) -> {
            return permission.hasPermission(2);
        }).then(net.minecraft.server.v1_16_R1.CommandDispatcher.a("target", ArgumentEntity.d()).then(net.minecraft.server.v1_16_R1.CommandDispatcher.a("allow").executes((commandcontext) -> {
            return execute(commandcontext.getSource(), ArgumentEntity.f(commandcontext, "target"));
        })));

        dispatcher.register(literalArgumentBuilder);
    }

    private static int execute(CommandListenerWrapper commandListenerWrapper, Collection<EntityPlayer> players) {
        for(EntityPlayer entityPlayer : players) {
            entityPlayer.abilities.canFly = true;
            entityPlayer.updateAbilities();
        }

        if(players.size() == 1) {
            commandListenerWrapper.sendMessage(new ChatMessage("%s を飛行可能にしました", new Object[] {players.iterator().next().getScoreboardDisplayName()}), true);
        }else {
            commandListenerWrapper.sendMessage(new ChatMessage("%s 体のプレイヤーを飛行可能にしました", new Object[] {players.size()}), true);
        }

        return players.size();
    }
}

public static void register(CommandDispatcher<CommandListenerWrapper> dispatcher) {

・コマンド登録用のメソッドを作ります。
CommandDispatchercom.mojang.brigadier.CommandDispatcher の方です

LiteralArgumentBuilder<CommandListenerWrapper> literalArgumentBuilder = net.minecraft.server.v1_16_R1.CommandDispatcher.a("fly").requires((permission) -> {
    return permission.hasPermission(2);
}).then(net.minecraft.server.v1_16_R1.CommandDispatcher.a("target", ArgumentEntity.d()).then(net.minecraft.server.v1_16_R1.CommandDispatcher.a("allow").executes((commandcontext) -> {
    return execute(commandcontext.getSource(), ArgumentEntity.f(commandcontext, "target"));
})));

net.minecraft.server.v1_16_R1.CommandDispatcher.a("fly")
/flyの部分です。

requires((permission) -> {
return permission.hasPermission(2);
});

コマンドを実行できる権限レベルを指定しています。2 を指定すると管理者権限を持つプレイヤーのみ実行できます。

then(net.minecraft.server.v1_16_R1.CommandDispatcher.a("target", ArgumentEntity.d()).then(net.minecraft.server.v1_16_R1.CommandDispatcher.a("allow"))
/flyの後の構文です
ArgumentEntity.d()はプレイヤーのみを指定させたい時に使います。もし、@eなどエンティティを指定した場合このようにエラーが出るようになります。無題.jpg

executes((commandcontext) -> {
    return execute(commandcontext.getSource(), ArgumentEntity.f(commandcontext, "target"));
})

・/fly <プレイヤー> allowが実行された時の処理を記述します。
getSource()CommandListenerWrapper を取得できます。
ArgumentEntity.f(commandcontext, "target")Collection<EntityPlayer>を取得できます。

dispatcher.register(literalArgumentBuilder);

・コマンドの構文を登録しています。

for(EntityPlayer entityPlayer : players) {
    entityPlayer.abilities.canFly = true;
    entityPlayer.updateAbilities();
}

entityPlayer.abilities.canFly = trueで飛行可能にできます。
entityPlayer.updateAbilities()はサーバーにプレイヤーの能力(entityPlayer.abilities)に変更があったことを送信します。変更した際は必ずこれを記述しましょう。

if(players.size() == 1) {
    commandListenerWrapper.sendMessage(new ChatMessage("%s を飛行可能にしました", new Object[] {players.iterator().next().getScoreboardDisplayName()}), true);
}else {
    commandListenerWrapper.sendMessage(new ChatMessage("%s 体のプレイヤーを飛行可能にしました", new Object[] {players.size()}), true);
}

commandListenerWrapper.sendMessage()はコマンド送信者にメッセージを送信します。
getScoreboardDisplayName()を使うとこのようにカーソルを合わせた際に情報が表示されるようになります。無題1.jpg
trueを指定すると他のプレイヤーや、サーバーログなどにも送信されます。
falseを指定するとコマンド送信者のみ送信されます。

Main.java
package com.hamusuke.qiita;

import org.bukkit.Bukkit;
import org.bukkit.craftbukkit.v1_16_R1.CraftServer;
import org.bukkit.plugin.java.JavaPlugin;

import com.hamusuke.qiita.command.FlyCommand;
import com.mojang.brigadier.CommandDispatcher;

import net.minecraft.server.v1_16_R1.CommandListenerWrapper;
import net.minecraft.server.v1_16_R1.DedicatedServer;

public class Main extends JavaPlugin {
    @Override
    public void onEnable() {
        DedicatedServer dedicatedServer = ((CraftServer)Bukkit.getServer()).getServer();
        CommandDispatcher<CommandListenerWrapper> dispatcher = dedicatedServer.vanillaCommandDispatcher.a();

        FlyCommand.register(dispatcher);
        getLogger().info("registered fly command");
    }
}

・プラグインがロードされた時にonEnable()が呼び出されるので、ここでコマンドを登録します。

サーバーに入れてみる

無題3.jpg
しっかり登録できています。
無題2.jpg
飛行できます。

バニラコマンドとして登録されているので、/minecraft:flyになります。(minecraft:は省略可能)

良いところ

・バニラコマンドと同じように機能する。
・plugin.ymlに記述しなくても良い。
・タブ補完用のクラスを作らなくても良い

悪いところ

・コードが長くなりがちである。

終わりに

記事を見ていただきありがとうございます。
記事を書いたのは初めてなので、分かりにくいところがあったかも知れません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

enumの比較は==がつよつよ、equalsはよわよわ【Java】

理由

==はNullPointerExceptionが発生しない/ equalsは発生する

ぬるぽ確認
public class EnumNullPointerExceptionTest {

    enum Season { SPRING, SUMMER, AUTUMN, WINTER; }

    public static void main(String[] args) {
        Season season = null;
        if (season != Season.SPRING) {
            System.out.println("季節は春ではありません。");
        }

        if (!season.equals(Season.SPRING)) {
            System.out.println("季節は春ではありません。");
        }
    }
}
実行結果
季節は春ではありません。
Exception in thread "main" java.lang.NullPointerException
    at EnumNullPointerExceptionTest.main(EnumNullPointerExceptionTest.java:13)

==は通って、equlasでぬるぽで怒られた(実際に通ったのは、!=だけども)。

NullPointerExceptionが発生しない==つよつよ:blush:
NullPointerExceptionが発生しちゃうequlasよわよわ:sweat:

==は型変換がないため無駄がない(速い)/ equalsはObject型への型変換があるため無駄がある(遅い)

==の処理時間計測
public class EnumSpeedTest1 {

    enum Season { SPRING, SUMMER, AUTUMN, WINTER; }

    public static void main(String[] args) {
        Season season = Season.SPRING;

        long startTimeMs = System.currentTimeMillis();
        for (int i = 0; i < 2000000000; i++) {
            if (season == Season.SPRING) {}
        }
        long executionTimeMs = System.currentTimeMillis() - startTimeMs;
        System.out.println("比較を20億回繰り返すのにかかった時間は" + executionTimeMs + "ミリ秒です");
    }
}
==の実行結果
比較を20億回繰り返すのにかかった時間は2ミリ秒です
equlasの処理時間計測
public class EnumSpeedTest2 {

    enum Season { SPRING, SUMMER, AUTUMN, WINTER; }

    public static void main(String[] args) {
        Season season = Season.SPRING;

        long startTimeMs = System.currentTimeMillis();
        for (int i = 0; i < 2000000000; i++) {
            if (season.equals(Season.SPRING)) {}
        }
        long executionTimeMs = System.currentTimeMillis() - startTimeMs;
        System.out.println("比較を20億回繰り返すのにかかった時間は" + executionTimeMs + "ミリ秒です");
    }
}
equalsの実行結果
比較を20億回繰り返すのにかかった時間は4ミリ秒です

わずかだけど、==の方は型変換しないから速いね。

型変換がなくて無駄がない==つよつよ:relaxed:
型変換が必要で無駄があるequalsよわよわ:sweat:

==はコンパイル時に型チェックがある/ equalsは型チェックない

コンパイル確認
public class EnumCompileTest {

    enum Season { SPRING, SUMMER, AUTUMN, WINTER; }
    enum Color { RED, GREEN, BLUE; }
    enum Animal { DOG, CAT; }

    public static void main(String[] args) {
        if (Season.SPRING.equals(Color.RED)) {}
        if (Season.SPRING == Animal.CAT) {}
    }
}
コンパイル結果
$ javac EnumCompileTest.java
EnumCompileTest.java:10: エラー: 型SeasonとAnimalは比較できません
        if (Season.SPRING == Animal.CAT) {}
                          ^
エラー1個

型が違う==の比較は怒られた。

コンパイル時に型チェックがある==つよつよ:relaxed:
コンパイル時に型チェックないequalsよわよわ:sweat:

番外:なぜenumは==で比較できるのか?

言語仕様には、こう書いてあるよ。
8.9.1. Enum Constants

Because there is only one instance of each enum constant, it is permitted to use the == operator in place of the equals method when comparing two object references if it is known that at least one of them refers to an enum constant.

there is only one instance of each enum constantってことは、
enumのインスタンスはシングルトンなんだね。だから、==で比較できると。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(学習メモ)Java2級対策:問5要点(演算結果)

(自己学習用)

~~~ TBE ~~~

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java StringBuilderクラスについて

StringBuilderクラス、の『クラス』とは、Javaに標準装備されている機能のこと、という理解で正しいでしょうか?

また、「プログラムを実行するための処理をまとめたオブジェクト」、という意味のクラスとは別の用語なのか否かも教えて欲しいです。

拙い投稿申し訳ありません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cask+anyenv+jenvを使ったJavaのバージョン管理

環境

OS:macOS Catalina 10.15.5
brew:2.4.3

caskを使ったJavaのインストール

brew caskをそのまま使うと古いバージョンのJavaをインストールができない。
そのため以下のコマンドでを使って古いバージョンもインストールできるようにする。

$ brew tap homebrew/cask-versions

各種バージョンをインストールする

// インストール可能なjavaのバージョンの確認
$ brew search java

// java14
$ brew cask install java

// java11
$ brew cask install java11

// java8
$ brew cask install adoptopenjdk8

// javaのバージョン確認
$ /usr/libexec/java_home -V

anyenvのインストール

anyenvは*envを管理することができる。今回の場合はjenvを管理するために導入する。

// brewでanyenvをインストール
$ brew install anyenv

// パス追加 zsh版
$ echo 'eval "$(anyenv init -)"' >> ~/.zshrc

// マニフェストディレクトリ作成
$ anyenv install --init
これで.anyenvディレクトリが作成される。

// SHELLの再起動
$ exec $SHELL -l

// anyenvコマンド
// インストールした*envのバージョン確認
$ anyenv versions

// anyenvでインストールできる*envのリスト
$ anyenv install -l

※うまいことzshrcの設定が反映されない場合はsource ~/.zshrcで反映する

便利なanyenvのプログインを入れる

プラグインは任意ですが入れておくと便利です。

// プラグイン用のディレクトリ作成
$ mkdir -p ~/.anyenv/plugins

// anyenv-update anyenvで入れた*envを一括でアップデートしてくれるプラグイン
$ git clone https://github.com/znz/anyenv-update.git ~/.anyenv/plugins/anyenv-update

// anyenv-git anyenvで入れた*envをgitコマンドで実行できるプラグイン
$ git clone https://github.com/znz/anyenv-git.git ~/.anyenv/plugins/anyenv-git

jenvのインストール

caskでインストールしたJavaを管理する

// jenvをインストール
$ anyenv install jenv

// SHELLの再起動
$ exec $SHELL -l

// 確認
$ anyenv versions
jenv:
  system
  1.8
  1.8.0.252
  11
  11.0
  11.0.2
  14
  14.0
* 14.0.1 (set by /Users/masa/.anyenv/envs/jenv/version)
  openjdk64-1.8.0.252
  openjdk64-11.0.2
  openjdk64-14.0.1

Javaをjenvで管理する設定

各種Javaのバージョンをjenvに登録して切り替える方法です。

// 紐付け jenvにaddする
$ jenv add `/usr/libexec/java_home -v "1.8"`
$ jenv add `/usr/libexec/java_home -v "11"`
$ jenv add `/usr/libexec/java_home -v "14"`

// jenvで管理しているjavaのバージョン確認
$ jenv versions

// global設定
$ jenv global 11.0.2

// javaのバージョン確認
$ java versions

// シェル起動
$ jenv exec zsh

// JAVA_HOMEを自動で設定するようにする
$ jenv enable-plugin export

// JAVA_HOME確認
$ echo $JAVA_HOME

// Topic 特定のディレクトリ内で別バージョンのJDKを利用する場合
$ jenv local 11.0.2

※JAVA_HOMEの設定がうまいこと反映されない場合はterminalを再起動等すれば反映されるときがあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[servlet][JSP][tomcat]について

はじめに

現在、servlet/JSP/tomcatについてpaizaで勉強中です。
アウトプットの一環としてキータを書いていきます。

servletとは

WEBサーバー上で動く、JAVAで書かれたプログラムのことである。
WEBブラウザからのリクエストに応えて、処理を行うのが仕事である。

JSPとは

servletは内部の処理をしていたが、JSPは画面に文字を表示させる仕事をする。
HTMLの中にJAVAを埋め込み動的にWEBを表示させる。
railsで言うところのerbファイルである。

tomcatとは

servletやJSPを動かすときに使うサーブレットコンテナのことである。
HTTPリクエストが来たらservletに命令して、動くようにしている。

servlet/JSPの基本的な書き方

Javaファイルの作成

tomcat/webapps/該当ファイル名の中にHelloWorld.javaのようなファイルを作成。

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

         // 後ほど作成するJSPファイルを読み込めるように設定
        String view = "/WEB-INF/views/index.jsp";
        RequestDispatcher dispatcher = request.getRequestDispatcher(view);
        dispatcher.forward(request, response);
    }
}

コンパイルする

書き終わったらコンパイルする。

> cd tomcat/webapps/該当ファイル名

> javac -classpath "../../lib/servlet-api.jar" -d WEB-INF/classes HelloWorld.java

tomcatの再起動を不要にする

tomcat/conf/Catarina/localhostにmywork.xmlを作成して、以下を記述。

<Context
reloadable="true"/>

jspファイルを作成する

tomcat/webapps/該当ファイル名/WEB-INFにviewsディレクトリを作成する。
そしてその中にindex.jspを作成。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hello World</title>
    </head>
    <body>
        <h1>Hello World</h1>
    </body>
</html>

ルーティングを指定する。

tomcat/webapps/該当ファイル名/WEB-INFの中にweb.xmlを作成。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
        http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
        version="4.0" metadata-complete="true">

    <servlet>
        <servlet-name>Hello</servlet-name>
        <servlet-class>HelloWorld</servlet-class> //先ほど作成したjavaファイルのclass名
    </servlet>
    <servlet-mapping>
        <servlet-name>Hello</servlet-name>
        <url-pattern>/hello</url-pattern> //パスを記述
    </servlet-mapping>

</web-app>

これで該当ファイル/helloと言うurlでアクセスするとHello Worldが表示される。

JSPのタグ

JSPにJAVAを埋め込むときに使用するタグの種類は下記の通りです。
railsで言うところの<% %>の部分です。

// コードの実行
<% %> 
// コードを実行し、結果を出力
<%= %>
// JSPの宣言(index.jspの先頭に使った)
<%@ %>
// メソッドや変数の宣言
<%! %>
// コメント
<%- -%>

servletからJSPにデータを送り、表示させる。

servletに

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorld extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
        throws IOException, ServletException {

       // requestは上のHttpServletRequestのこと。
       // JSPでmessageを使うとHello worldが表示される。setAttributeなので注意。
       request.setAttribute("message", "Hello world");
    }
}

JSP側は

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hello Template</title>
    </head>
    <body>

        // requestオブジェクトから先ほどのmessageを取り出す。こちらはgetAttributeなので注意。
        // String型にキャストして、String型のmesageに代入して、したで表示。
        <% String message = (String)request.getAttribute("message");%>
        <p><%= message %></p>
    </body>
</html>

終わりに

簡単でしたが、まとめました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javaのメソッド

はじめに

前回の配列の投稿でスパム認定されましたこんにちは。

メソッド

・メソッドとは部品だったりゲームの技ようなもの(クラスはステータスのような※あくまで私の感覚)。
クラスの{}(ブロック)の中に書かないと動かない
・メソッドを使うことによって同じ処理などする時なんども同じ処理を書かずに呼ぶだけで済むので楽。
・コードの見通しや管理が楽になる。

kane.java
public static void メソッド名() {
 実行する処理 ;
}
kane.java
class Main{
   public static void main(String[]args) {
    banana() ;  //()は必要。
   }

   public static void banana() {
    System.out.println("黄色") ;
   }
}

・戻り値とは
呼び出されたメソッドから、呼び出し元のメソッドへ返す値(データ)のことを戻り値または返り値という。

public static void main(String[] args) { }

javaのプログラムを実行した場合自動でmainメソッドから始まる。
・main メソッドが各メソッドに指示を出し、支持された各メソッドが個々の処理を実行する。

・忘れず書いとけば今はおK。

引数

・メソッドに与える追加情報のようなもの。
・メソッドを呼び出す際に呼び出し元から値を渡すことができる。
・それによって渡されたメソッド内でもその値が使えるようになる

引数を受け取れるメソッドを定義するために、メソッドの定義部分で、引数を受け取るための箱となる変数(仮引数(かりひきすう))を指定し public static void banana() の()に仮引数を指定する。
仮引数は、変数定義と同様に、データ型を指定する必要があります

kane.java
class Main{
   public static void main(String[]args) {
    banana("赤") ;  
   }

   public static void banana(String color) { //データ型を指定
    System.out.println("このバナナの色は" + color + "です。") ;
   }
}

何も引き渡す情報がなかったとしても ( ) は必要なので必ず記入する。

・複数の引数を渡す場合

kane.java
class Main{
   public static void main(String[]args) {
    banana("フィリピン", 5) ;  
   }

   public static void banana(String origin, int year) { 
    System.out.println("このバナナは" + origin + "産の" + year + "年ものです。") ;
   }
}

実行結果
このバナナはフィリピン産の5年ものです//それは腐ってます

戻り値

オーバーロード

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語別Stack処理速度比較

1. はじめに

Go言語にはStackが実装されていませんが、

Stackの処理自体は、どの言語でも、
配列や線形リストを使って自作で実装することができます。

Go言語向けに自作で作ったついでに、
Stackが実装されている他言語との処理速度(処理時間)を比較してみました。

実験で使用した言語は、

  • C#(.NET Core3.0)
  • go1.14.4
  • java 13.0.2
  • Python 3.8.3
  • C言語(MinGW.org GCC-8.2.0-3)(2020/07/12追記)

の5言語です。

各言語のソースは、githubでも公開しています。
https://github.com/NobuyukiInoue/StackSpeedTest

2. 言語別のメソッド比較

Stackといっても、言語によって、各種操作がメソッドで提供されていたり、
プロパティでチェックしなければならないなど、細かな処理の実装内容が異なります。

今回の実験で使用した各言語でのクラス/インタフェース/機能を一覧にすると、以下のようになります。

処理内容 C#(.NET Core3.x)
(Stackクラス)
Java 8
(クラスStack)
Java 8
(クラスArrayDeque)
Java 8
(クラスDeque)
Python3.8.3
(リスト)
Stackへの書き込み Pushメソッド pushメソッド pushメソッド addFirstメソッド append関数
Stackからの取り出し Popメソッド popメソッド popメソッド removeFirstメソッド pop関数
値の検索 Containsメソッド
(戻り値はbool型)
searchメソッド
(戻り値はindex番号)
Containsメソッド
(戻り値はbool型)
Containsメソッド
(戻り値はbool型)
index関数
Stackが空かどうか調べる Countプロパティを利用 emptyメソッド isEmptyメソッド isEmptyメソッド len関数を利用

3. 検証環境と実験結果(2020/07/11追記)

項目
OS MS-Windows 10
CPU Intel Core-i7 8559u
メモリ 16GB(LPDDR3/2133MHz)

メモリ容量を追記しました。(2020/07/11)

処理の内容は、

  • 1~1億までの数値のpush()
  • 最も深い位置にある値1の検索
  • 1億~1までのPop()

です。

1億個の数値をint64(8byte)で割り当てると、762MByteの容量が必要になります。
int32(4byte)なら、半分の381MByteですね。

生成するスタックの容量が小さいうちは、それほど処理時間に差はありませんが、
1億個のスタック生成となると、選択した方法によってはメモリ消費が4GBを超えます。

搭載されているメモリが8GB以下だと、言語によっては(デフォルト設定では)、
ヒープメモリが足りないといったエラーがでます。

各言語での処理時間は以下の通りでした。

消費したメモリについても、Windows10のリソースモニター(コミット値)による表示の目視ではありますが、参考程度に掲載しています。(2020/07/11追記)

C# NET Core 3.0.100

Stackクラスを使用
https://docs.microsoft.com/ja-jp/dotnet/api/system.collections.generic.stack-1?view=netcore-3.1

メモリ消費は0.39GBでした。

times Execution time
1st 977 [ms]
2nd 1003 [ms]
3rd 1004 [ms]

メモリ消費からみて、内部ではint32で処理が行われているようです。

他言語と比べてかなりメモリ消費が少なく、
処理データの少なさが処理時間の短さに影響しているようです。

今回の比較では最速の数値です。

go1.14.4 windows/amd64(配列版)

メモリ消費は2.36GBでした。

times Execution time
1st 2089 [ms]
2nd 1853 [ms]
3rd 1737 [ms]

配列の再割り当てを繰り返しても、それほど遅くはないようです。
Javaより速い結果となりました。

go1.14.4 windows/amd64(線形リスト版)

メモリ消費は1.49GBでした。

times Execution time
1st 5617 [ms]
2nd 5400 [ms]
3rd 5569 [ms]

処理時間は配列版よりも遅くなりました。

線形リストでは次ノードへのポインタが必要なので、
直線的なメモリ配置の配列より不要なデータが増えてしまいますが、
配列版より消費が少ない結果となりました。

Go言語では、配列の再割り当てを行っても、
プログラムの終了までメモリの解放が行われていないのかもしれません。
(あくまで推測で、だれか検証してほしい)

java 13.0.2(クラスStack版)

クラスStackを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Stack.html

メモリ消費は4.21GBでした。

times Execution time
1st 6743 [ms]
2nd 6690 [ms]
3rd 6733 [ms]

java 13.0.2(クラスArrayDeque版)

クラスArrayDequeを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/ArrayDeque.html

メモリ消費は3.96GBでした。

times Execution time
1st 4397 [ms]
2nd 4612 [ms]
3rd 4477 [ms]

クラスStackよりも処理時間が短くなりました。

java 13.0.2(インタフェースDeque版)

インタフェースDequeを使用
https://docs.oracle.com/javase/jp/8/docs/api/java/util/Deque.html

メモリ消費は4.31GBでした。

times Execution time
1st 21416 [ms]
2nd 20187 [ms]
2nd 20341 [ms]

クラスStackよりも処理時間が増えてしまいました。

Python 3.8.3

リストを使用

メモリ消費は4.42GBでした。

times Execution time
1st 16957 [ms]
2nd 16561 [ms]
3rd 17558 [ms]

他言語では、一度確保したメモリは消費したままでしたが、
Python3では、1億個のPush直後がメモリ消費の最大値で、
Pop処理が進むにしたがって消費メモリが減っていく様子が目視できます。

C言語(64K個単位のセグメント(配列)でStackを実装)(2020/07/12追記)

メモリ消費は0.38GBでした。

さすがにC言語は速いですね。
といっても、C#に負けてしまいました。

確保したブロックは破棄せずにそのまま保持するようにすれば、もっと速くなるかもしれません。

times Execution time
1st 1591 [ms]
2nd 1554 [ms]
3rd 1558 [ms]

C言語(線形リストでStackを実装)(2020/07/12追記)

メモリ消費は1.55GBでした。

Push処理、Pop処理、それぞれ5秒程度ずつといったところでしょうか。
メモリの確保・解放の処理回数が多いので、それだけ処理時間が遅くなっています。

times Execution time
1st 10981 [ms]
2nd 11066 [ms]
3rd 10799 [ms]

関連(2020/07/11追記)

LeetCodeのProblemの中にも、Stackを自分で実装する問題があります。
いろんな言語での回答例が投稿されているので、処理時間を比較するのも面白いと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む