8. スタック
著者:梅谷 武
各言語においてスタックの基本動作を確認し、比較する。
作成:2024-09-17
更新:2024-10-25
更新:2024-10-25
スタックとはデータを後入れ先出し(LIFO: Last In, First Out)で管理するデータ構造である。Racketで簡単な例を示す。ここではtyped-stackを利用する。typed-stackは静的型付けされたTyped Racket用パッケージであるがRacketでも限定的に使える。typed-stackは不変(immutable)、可変(mutable)の両方に対応しており、次のような機能がある。
- 空スタック生成: (empty-stack)
- 先頭要素取得: (top stk)
- push(immutable): (push stk value)
- push(mutable): (push! stk value)
- pop(immutable): (pop stk)
- pop(mutable): (pop! stk)
- リスト変換: (stack->list stk)
- 空スタック?: (stack-empty? stk)
- スタック長: (stack-length stk)
> (require typed-stack)
> (define stk (empty-stack))
> stk
(Stack '())
> (push stk 0)
(Stack '(0))
> (push! stk 0)
> stk
(Stack '(0))
> (push stk 1)
(Stack '(1 0))
> (push! stk 1)
> stk
(Stack '(1 0))
> (push stk 2)
(Stack '(2 1 0))
> (push! stk 2)
> stk
(Stack '(2 1 0))
> (stack->list stk)
'(2 1 0)
> (top stk)
2
> (pop stk)
2
(Stack '(1 0))
> (pop! stk)
2
> (pop stk)
1
(Stack '(0))
> (pop! stk)
1
> (pop stk)
0
(Stack '())
> (pop! stk)
0
> stk
(Stack '())
> (stack-empty? stk)
#t
> (stack-length stk)
0
同じことをOCamlでやってみる。標準ライブラリのStackモジュールはmutableなスタックを提供する。スタックをリストに変換する機能はない。
- 空スタック生成: Stack.create
- 先頭要素取得: Stack.top stk
- push(mutable): Stack.push value stk
- pop(mutable): Stack.pop stk
- 空スタック?: Stack.is_empty stk
- スタック長: Stack.length stk
# let stk = Stack.create ();;
val stk : '_weak1 Stack.t =
# Stack.push 0 stk;;
- : unit = ()
# Stack.push 1 stk;;
- : unit = ()
# Stack.push 2 stk;;
- : unit = ()
# Stack.top stk;;
- : int = 2
# Stack.length stk;;
- : int = 3
# Stack.pop stk;;
- : int = 2
# Stack.pop stk;;
- : int = 1
# Stack.pop stk;;
- : int = 0
# Stack.length stk;;
- : int = 0
# Stack.is_empty stk;;
- : bool = true
std::stackを用いる。
Module Stackを用いる。
typed-stackを用いる。
繰り返す回数nを与える。
- 空スタック:stkを作る。
- pushをn回繰り返し、stkの長さを見る。
- popをn回繰り返し、stkが空になったことを見る。
stack.cpp:
#include <chrono>
#include <print>
#include <stack>
#include <stdexcept>
void test(long n) {
// push
auto start = std::chrono::high_resolution_clock::now();
std::stack<long> stk;
for (long i = 0l; i < n; ++i)
stk.push(i);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = end - start;
std::println("length = {}", stk.size());
std::println("push time: {:9.6f} [sec]", duration.count());
// pop
start = std::chrono::high_resolution_clock::now();
while (!stk.empty())
stk.pop();
end = std::chrono::high_resolution_clock::now();
duration = end - start;
std::println("length = {}", stk.size());
std::println("pop time: {:9.6f} [sec]", duration.count());
}
int main(int argc, char *argv[]) {
try {
if (argc != 2) {
throw std::invalid_argument(
"Usage: stack <positive integer>");
}
long n = std::stol(argv[1]);
if (n <= 0l) {
throw std::invalid_argument(
"The argument must be a positive integer.");
}
test(n);
} catch (const std::logic_error &e) {
std::println(stderr, "Logic error: {}", e.what());
} catch (const std::runtime_error &e) {
std::println(stderr, "Runtime error: {}", e.what());
} catch (const std::exception &e) {
std::println(stderr, "Another std::exception: {}", e.what());
} catch (...) {
std::println(stderr, "Unknown exception.");
}
return 0;
}
makefile:
CPPG = g++
CPPL = clang++
CPPFLAGS = -std=c++23 -O2
SRC = stack.cpp
EXES = stack_g stack_l
all: $(EXES)
stack_g: $(SRC)
$(CPPG) $(CPPFLAGS) -o $@ $(SRC)
stack_l: $(SRC)
$(CPPL) $(CPPFLAGS) -o $@ $(SRC)
clean:
rm -f $(EXES)
n = 10000000で測定する。
$ measure ./stack_g 10000000
length = 10000000
push time: 0.060265 [sec]
length = 0
pop time: 0.014986 [sec]
======================================
Process exited with status: 0
total time: 0.084146 [sec]
mem size: 84944 [KB]
code size: 128 [KB]
$ measure ./stack_l 10000000
length = 10000000
push time: 0.083953 [sec]
length = 0
pop time: 0.018322 [sec]
======================================
Process exited with status: 0
total time: 0.110900 [sec]
mem size: 84992 [KB]
code size: 101 [KB]
stack.ml:
open Printf
let test n =
(* push *)
let start = Unix.gettimeofday () in
let stk = Stack.create () in
for i = 0 to n - 1 do
Stack.push i stk
done;
let end_ = Unix.gettimeofday () in
printf "length = %d\n" (Stack.length stk);
printf "push time: %9.6f [sec]\n" (end_ -. start);
(* pop *)
let start = Unix.gettimeofday () in
while not (Stack.is_empty stk) do
ignore (Stack.pop stk)
done;
let end_ = Unix.gettimeofday () in
printf "length = %d\n" (Stack.length stk);
printf "pop time: %9.6f [sec]\n" (end_ -. start)
let () =
try
if Array.length Sys.argv <> 2 then
raise (Invalid_argument
"Usage: stack <positive integer>")
else
let n = int_of_string Sys.argv.(1) in
if n <= 0 then
raise (Invalid_argument
"The argument must be a positive integer.")
else
test n
with
| Invalid_argument msg ->
eprintf "Logic error: %s\n" msg
| Failure msg ->
eprintf "Runtime error: %s\n" msg
| exn ->
eprintf "Another exception: %s\n" (Printexc.to_string exn)
makefile:
OCAMLOPT = ocamlopt
OCAMLFLAGS = -O2
LIBPATH = -I +unix
LIBS = unix.cmxa
SRC = stack.ml
EXES = stack
all: $(EXES)
stack: $(SRC)
$(OCAMLOPT) $(OCAMLFLAGS) $(LIBPATH) -o $@ $(LIBS) $(SRC)
clean:
rm -f $(EXES) *.o *.cmx *.cmi
n = 10000000で測定する。
$ measure ./stack 10000000
length = 10000000
push time: 0.630650 [sec]
length = 0
pop time: 0.598853 [sec]
======================================
Process exited with status: 0
total time: 1.251666 [sec]
mem size: 288556 [KB]
code size: 3298 [KB]
stack.rkt:
#lang racket
(require iso-printf)
(require typed-stack)
(define (test n)
; push
(define start (current-inexact-milliseconds))
(define stk (empty-stack))
(for ([i (in-range n)])
(push! stk i))
(define end (current-inexact-milliseconds))
(printf "length = %d\n" (stack-length stk))
(printf "push time: %9.6f [sec]\n" (/ (- end start) 1000.0))
; pop
(set! start (current-inexact-milliseconds))
(let loop ()
(unless (stack-empty? stk)
(pop! stk)
(loop)))
(set! end (current-inexact-milliseconds))
(printf "length = %d\n" (stack-length stk))
(printf "pop time: %9.6f [sec]\n" (/ (- end start) 1000.0)))
(define (main args)
(with-handlers
([exn:fail? (λ (e) (eprintf "%s\n" (exn-message e)))]
[exn? (λ (e) (eprintf "Unexpected: %s\n" (exn-message e)))])
(cond
[(not (= (vector-length args) 1))
(error "Usage: stack <positive integer>")]
[else
(let ([n (string->number (vector-ref args 0))])
(cond
[(not (integer? n))
(error "The argument must be an integer.")]
[(<= n 0)
(error "The argument must be a positive integer.")]
[else
(test n)]))])))
(main (current-command-line-arguments))
n = 1000で測定する。
$ raco exe stack.rkt
$ measure ./stack 1000
length = 1000
push time: 0.395930 [sec]
length = 0
pop time: 0.950207 [sec]
======================================
Process exited with status: 0
total time: 2.315375 [sec]
mem size: 218884 [KB]
code size: 25841 [KB]
typed-stackはかなり時間がかかり、n = 1000程度が限界である。
Racketのtyped-stackは仕様が大きく、かなり重いので単純で軽い可変スタックを実装してみる。
stack2.rkt:
#lang racket
(require iso-printf)
(define (make-stack)
(box '()))
(define (push! stack item)
(set-box! stack (cons item (unbox stack))))
(define (pop! stack)
(let ([current-stack (unbox stack)])
(if (null? current-stack)
(error "Cannot pop from an empty stack")
(begin
(set-box! stack (cdr current-stack))
(car current-stack)))))
(define (top stack)
(let ([current-stack (unbox stack)])
(if (null? current-stack)
(error "Cannot peek into an empty stack")
(car current-stack))))
(define (stack-empty? stack)
(null? (unbox stack)))
(define (stack-length stack)
(length (unbox stack)))
(define (test n)
; push
(define start (current-inexact-milliseconds))
(define stk (make-stack))
(for ([i (in-range n)])
(push! stk i))
(define end (current-inexact-milliseconds))
(printf "length = %d\n" (stack-length stk))
(printf "push time: %9.6f [sec]\n" (/ (- end start) 1000.0))
; pop
(set! start (current-inexact-milliseconds))
(let loop ()
(unless (stack-empty? stk)
(pop! stk)
(loop)))
(set! end (current-inexact-milliseconds))
(printf "length = %d\n" (stack-length stk))
(printf "pop time: %9.6f [sec]\n" (/ (- end start) 1000.0)))
(define (main args)
(with-handlers
([exn:fail? (λ (e) (eprintf "%s\n" (exn-message e)))]
[exn? (λ (e) (eprintf "Unexpected: %s\n" (exn-message e)))])
(cond
[(not (= (vector-length args) 1))
(error "Usage: stack2 <positive integer>")]
[else
(let ([n (string->number (vector-ref args 0))])
(cond
[(not (integer? n))
(error "The argument must be an integer.")]
[(<= n 0)
(error "The argument must be a positive integer.")]
[else
(test n)]))])))
(main (current-command-line-arguments))
n = 10000000で測定する。
$ raco exe stack2.rkt
$ measure ./stack2 10000000
length = 10000000
push time: 0.534337 [sec]
length = 0
pop time: 0.070072 [sec]
======================================
Process exited with status: 0
total time: 1.171759 [sec]
mem size: 294452 [KB]
code size: 12445 [KB]
測定結果を表にまとめる。単位は秒[sec]。
測定項目 | GCC | Clang | OCaml | Racket |
---|---|---|---|---|
push | 0.060 | 0.084 | 0.630 | 0.534 |
pop | 0.015 | 0.018 | 0.599 | 0.070 |
Racketは8.4 可変スタック実装で測定したもの。
This document is licensed under the MIT License.
Copyright (C) 2024 Takeshi Umetani