view paper/chapter6.tex @ 34:aacf118c6a8c default tip

add file.
author Taninari YU <you@cr.ie.u-ryukyu.ac.jp>
date Mon, 03 Mar 2014 17:37:21 +0900
parents 7149e38f717c
children
line wrap: on
line source

\chapter{画面共有システムTreeVNCの評価}
\section{実験環境}
TreeVNCは多人数の同時接続を可能にするソフトウェアである。よって、評価を行うためには多数のコンピュータが必要となる。\\
今回は、学科の並列計算環境とOSの授業の時間を使用させてもらって実験を行った。\\
使用した並列計算環境を表\ref{tab:cluster_spec}に示す。\\
\begin{table}[!htbp]
\caption{検証に利用するVMWareクラスタの仕様}
\label{tab:cluster_spec}
\begin{center}
\begin{tabular}{|c||c|} \hline
名前 & 概要 \\ \hline \hline
CPU & Intel(R) Xeon(R) CPU X5650@2.67GHz \\ \hline
物理コア数 & 2 \\ \hline
論理コア数 & 4 \\ \hline
CPUキャッシュ & 12MB \\ \hline
Memory & 8GB \\ \hline
OS & CentOS 5.8 \\ \hline
HyperVisor & VMWare ESXi \\ \hline
\end{tabular}
\end{center}
\end{table}
\subsection{CUI Versionの作成}
TreeVNCはGUI(Graphical User Interface)を使用したアプリケーションである。並列計算環境はCUI(Character User Interface)なので、そのままだと実行することができない。\\
 そこで、TreeVNCからGUIを使用している部分を取り除いてCUI環境で実行できるようにする必要があったので作成することにした。\\
 Listing\ref{src:main}はTreeVNCのMain文の一部である。ここで継承されているJAppletは、GUIのコンポーネントなので、このクラスを使用するとCUI環境で実行することができない。\\
 CUI環境で実行するにはJAppletを継承していないクラスを作成する必要があるが、Listing\ref{src:getclass}のようなViewerクラスを受け渡す場所をすべてでCUIとGUIのクラスに対応しなければならない。\\
 この問題に対して、CUIとGUIの共通Interface(Listing\ref{src:interface})を作成し、このInterfaceを利用することで解決した。


\begin{lstlisting}[language=java,frame=lrbt,label=src:main,caption=TreeVNCのMainClass,numbers=left]
  public class Viewer extends JApplet implements Runnable, WindowListener
  {
    final ConnectionView connectionView = new ConnectionView(Viewer.this,
                                                                   connectionPresenter, hasJsch);
  }
\end{lstlisting}

\begin{lstlisting}[language=java,frame=lrbt,label=src:getclass,caption=Viewerの受け取り,numbers=left]
  public SwingViewerWindowFactory(boolean isSeparateFrame, boolean isApplet, Viewer viewer)
  {

  }
\end{lstlisting}

\begin{lstlisting}[language=java,frame=lrbt,label=src:interface,caption=ViewerImpl,numbers=left]
  public interface ViewerImpl 
  {
    public boolean getCuiVersion();
    public MyRfbProto getRfb();
    public void closeApp();
    public void run();
  }
\end{lstlisting}

\subsection{Capistrano}
今回の実験では、48台のサーバ上でCUI版のTreeVNCを立ち上げる必要がある。実験する度に、各サーバにログインしてアプリケーションを立ち上げるのは手間がかかりすぎてしまう。Capistranoを使用することで、この問題を解決することができる。\\
Capistranoは複数のサーバ上で同時に処理を実行するためのオープンソースなソフトウェアであり、Rubyを用いて作成されている。\\
 Capistranoを実行する際に使用するスクリプトをListing\ref{src:capistrano}に示す。\\
スクリプトはListing\ref{src:cap_run}として実行することができる。\\


\begin{lstlisting}[language=ruby,frame=lrbt,label=src:capistrano,caption=cap.rb,numbers=left]
set :user, "mass" //実行するユーザ

role :pall, "133.13.62.1" //ここに命令を送りたいマシンのアドレス

task :ls do // task名
  run "ls -la" // 実行したい命令
end
\end{lstlisting}

\begin{lstlisting}[language=ruby,frame=lrbt,label=src:cap_run,caption=capistranoの実行,numbers=left]
  % cap -f cap.rb ls
\end{lstlisting}

\section{木の深さによる遅延}
TreeVNCは、クライアントを木構造に配置し画像を配信している。
木の深さが深くなってしまうと、データが下に届くまでに遅延が発生してしまう可能性がある。
そこで、木の深さによる遅延がどの程度発生するのかを測定してみた。
\subsection{遅延の測定方法}
RFBプロトコルでは、送られてくるデータの先頭にどのような処理をするかの命令番号が入っている。\\
 表\ref{tb:message}は送られてくるメッセージの一覧である。\\
命令番号11(CheckDelay)はプロトコルを拡張して作成した命令である。

\begin{table}[htbp]
\caption{Rfbプロトコルと追加したメッセージ一覧}
\label{tb:message}
\begin{center}
  \begin{tabular}{|c||c|c|} \hline
    命令番号 & 名前 & 説明   \\ \hline
     0 & FrameBufferUpdate & 画像の更新情報\\ \hline
     1 & SetColourMapEntries & ピクセルフォーマットでColour Mapを使用\\ \hline
     2 & Bell & ビープ音機能\\ \hline
     11 & CheckDelay & 画像が届くまでのDelayを測定\\ \hline
  \end{tabular}
\end{center}
\end{table}

Listing\ref{src:delay_cli}、Listing\ref{src:delay_serv}は遅延を測るためのプログラムである。\\
Root NodeはSystem.currentTimeMillis()を用いて時間を取得し、Nodeへ送信する。
System.currentTimeMillis()は、システムの現在時刻をミリ秒(long型の数値)で取得する関数である。
Nodeはこの値を受け取ると、そのままサーバへ受け取った値を返す。
Root Nodeはクライアントからの返信を受け取るとSystem.currentTimeMillis()を取り、差分を出してDelayを求める。
遅延を測る頻度は、画像を50回送信するごとに1回である。
\begin{lstlisting}[language=java,frame=lrbt,label=src:delay_cli,caption=遅延を測るプログラム,numbers=left]
  BufferedReader is = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));
  DataOutputStream os = new DataOutputStream(echoSocket.getOutputStream());
  os.writeBytes("checkdelay\n");
  os.writeBytes(String.valueOf(buf.getLong(16))+"\n");
\end{lstlisting}

\begin{lstlisting}[language=java,frame=lrbt,label=src:delay_serv,caption=遅延を測るプログラム,numbers=left]
  Long delay = System.currentTimeMillis()-Long.parseLong(is.readLine()); 
\end{lstlisting}

\subsection{遅延の測定結果}
2分木で木を構成した場合、Node数が48台だと深さが6となる。\\
Root Nodeを起動し、並列計算環境48台を起動し、Root Nodeから一番下のNodeまでどのくらいの時間がかるのかを測定した。\\
表\ref{tab:delay} はデータを20回ほど測定し最遅値を取った遅延の表である。\\
\begin{table}[!htbp]
\caption{データ送信の遅延}
\label{tab:delay}
\begin{center}
\begin{tabular}{|c||c|} \hline
段数 & 最遅値 \\ \hline \hline
2 & 32ミリ秒\\ \hline
4 & 244ミリ秒\\ \hline
6 & 446ミリ秒\\ \hline
\end{tabular}
\end{center}
\end{table}

\begin{figure}[!htbp]
\begin{flushleft}
\includegraphics[scale = 0.8]{images/hist.pdf}
\end{flushleft}
\caption{
  段差(step)によるデータの遅延
}
\label{fig:graph-late}
\end{figure}


並列計算環境のVMは高速なネットワークでつながっているので、遅延が少ない可能性がある。
そこで、実際にOSの授業で、どの程度の遅延があるのかを測定してみた。\\
OSの授業のときは25Nodeであったので、段数にすると5段である。
結果は、164ミリ秒と並列計算環境より良い結果が出た。

図\ref{fig:graph-late} は遅延の分布を示したヒストグラムである。X軸は試行の回数で、Y軸は遅延(ミリ秒)を表している。\\


\newpage
\section{画面のフリーズ}
データがTimeOutによってどの程度損失しているのかを調べてみた。\\
 今回、測定するために画像データのヘッダーの前にシリアルナンバーを付加した(Listing\ref{src:serial})。これによりNode側は、順番通りに画像が来なかった場合、データが損失したことを知ることができる。
Node側の確認用コードをListing\ref{src:timeout}に示す。このコードはRoot Nodeから流れてきたデータを受け取り自分の持っているcheckCounterと比較して、違う値が出ていればデータが損失していることになる。

\begin{lstlisting}[language=java,frame=lrbt,label=src:serial,caption=データの確認プログラム(Root Node側),numbers=left]
  ByteBuffer serialNum = ByteBuffer.allocate(8);
  serialNum.putLong(counter++);
  serialNum.flip();
  bufs.addFirst(serialNum);
  multicastqueue.put(bufs);  
\end{lstlisting}

\begin{lstlisting}[language=java,frame=lrbt,label=src:timeout,caption=データの確認プログラム(Node側),numbers=left]
  private void getLost(Reader reader) 
  {
    try 
    {
      long num = reader.readInt64();
      if(num != ++checkCounter) {
        System.out.println("LostData:"+(num - checkCounter));
        checkCounter = num;
      }
    } catch (TransportException e)  {
      e.printStackTrace();
    }
  }
\end{lstlisting}

現在の実装では、0.625秒データの読み込みがなければ、データをTimeOut Threadが読み込み、Node側には順番通りのデータが行かなくなるので画面がフリーズしたように見える。\\
 実験の結果、6段目のNodeでデータを受け取って表示してみた結果、データが損失するのを見ることができなかった。\\
データがロストしなのは良いことであるが、その分Root Nodeのメモリ上にデータがあるので、Root NodeがMemoryOverFlowを起こす可能性がある。私のディスプレイ環境(1920*1080)では、MemoryOverFlowが起こることはなかったが、Retinaディスプレイなどの高解像度ディスプレイを使用している場合は、MemoryOverFlowを起こす可能性がある。TimeOutの時間は今後調整が必要である。



\section{分木の最適化}
木の分木数が少ないほうが一つ一つのコンピュータにかかるCPU負荷が少なくなる。しかし、分木数を少なくしてしまうと、Nodeが増えたとき、木の深さが深くなり、データの伝搬に遅延が起こる可能性がある。
上記の実験の結果、木の深さが6のときのデータ伝搬に最遅446ミリ秒(0.446秒)の遅延しか起こっていなく、データの欠損も見られなかった。
分木数を変更してもコネクションの数はかわらないし、スイッチに対する負荷も変わらない。よって、100人程度で使用する場合は2分木が最適であるということがわかった。

\section{ZRLEとZRLEEのデータ圧縮率の比較}
作成したTreeVNCでは、従来のVNCで使用されているエンコードを使用しておらず、独自で作成しているZRLEEエンコードを使用している。\\
 一見ZRLEは辞書が一つでZRLEEは辞書が一つ一つの画像データに付加されていて、データ量はZRLEEのほうが多くなってしまっている可能性があるので、ZRLEEとZRLEのデータ量にどの程度の差が出るのかを調べてみた。
全く圧縮されていないRAWデータ,Zlib圧縮を使用しているZRLEE、ZRLEのデータ量の比較を行った。
図\ref{fig:compare_encoding}は1920 * 1080の画面の全描画にかかるデータ量を測った結果を示した図である。
ZRLEEの方がデータ量が少なくですんでいる。
これは、ZRLE(Zlib)が初めに送られた辞書を用いての解凍が余り有効的に働いていない場合があるからだと思われる。
つまりVNCの場合はZRLEEの様に毎回辞書のデータを付加させて送ってもデータ量に差がでない可能性があることが分かった。


\begin{figure}[!htbp]
\begin{center}
\includegraphics[scale = 0.8]{images/compare_encoding.pdf}
\end{center}
\caption{
RAW,ZRLE,ZRLEEによる1画面(1920*1080)描画にかかるデータ量。
x軸はピクセル数、y軸はバイト数を表している。
}
\label{fig:compare_encoding}
\end{figure}


\newpage 

\section{VNC Reflectorとの比較}
TreeVNCを用いて、Vnc Reflectorとの比較を行った。並列計算環境のVM48台を使用し、Bladeサーバの外にTreeVNCとVnc Reflectorを起動させたPCを置き、VM48台にアクセスさせ実験を行った。一極集中型の Vnc Reflectorは、48台繋がった時にスループットが2MBpsから5KBpsへ下がっていた、一方、TreeVNCのスループットは48台繋がっている状態でも2MBpsを保つことができていた。一極集中型で繋がっているVNC Reflectorと違い、TreeVNCは48台のアクセスを複数の通信網へ分散しCPU負荷をNode側の分散したいるので、人数が増えてもスループットを落とすことなくアプリケーションを実行することができる。