42

Haskell で記述されたサーバーと Qt (C++) で記述されたクライアントの 2 つのコンポーネントを含むアプリケーションを構築しています。私は倹約を使ってそれらを伝えていますが、なぜこんなに遅いのだろうかと思います。

私はパフォーマンステストを行いました。これが私のマシンでの結果です

結果

C++ server and C++ client:

Sending 100 pings                    -    13.37 ms
Transfering 1000000 size vector      -   433.58 ms
Recieved: 3906.25 kB
Transfering 100000 items from server -  1090.19 ms
Transfering 100000 items to server   -   631.98 ms

Haskell server and C++ client:

Sending 100 pings                       3959.97 ms
Transfering 1000000 size vector      - 12481.40 ms
Recieved: 3906.25 kB
Transfering 100000 items from server - 26066.80 ms
Transfering 100000 items to server   -  1805.44 ms

このテストで Haskell が遅いのはなぜですか? どうすればパフォーマンスを向上させることができますか?

ファイルは次のとおりです。

ファイル

パフォーマンス.倹約

namespace hs test
namespace cpp test

struct Item {
    1: optional string    name
    2: optional list<i32> coordinates
}

struct ItemPack {
    1: optional list<Item>     items
    2: optional map<i32, Item> mappers
}


service ItemStore {
    void ping()
    ItemPack getItems(1:string name, 2: i32 count) 
    bool     setItems(1: ItemPack items)

    list<i32> getVector(1: i32 count)
}

Main.hs

{-# LANGUAGE ScopedTypeVariables #-}   
module Main where

import           Data.Int  
import           Data.Maybe (fromJust) 
import qualified Data.Vector as Vector
import qualified Data.HashMap.Strict  as HashMap
import           Network

-- Thrift libraries
import           Thrift.Server

-- Generated Thrift modules
import Performance_Types
import ItemStore_Iface
import ItemStore


i32toi :: Int32 -> Int
i32toi = fromIntegral

itoi32 :: Int -> Int32
itoi32 = fromIntegral

port :: PortNumber
port = 9090

data ItemHandler = ItemHandler

instance ItemStore_Iface ItemHandler where
    ping _                   = return () --putStrLn "ping"
    getItems _ mtname mtsize = do 
        let size = i32toi $ fromJust mtsize
            item i = Item mtname (Just $ Vector.fromList $ map itoi32 [i..100])
            items = map item [0..(size-1)]
            itemsv = Vector.fromList items 
            mappers = zip (map itoi32 [0..(size-1)]) items 
            mappersh = HashMap.fromList mappers
            itemPack = ItemPack (Just itemsv) (Just mappersh)
        putStrLn "getItems"
        return itemPack

    setItems _ _             = do putStrLn "setItems"
                                  return True

    getVector _ mtsize       = do putStrLn "getVector"
                                  let size = i32toi $ fromJust mtsize
                                  return $ Vector.generate size itoi32

main :: IO ()
main = do
    _ <- runBasicServer ItemHandler process port 
    putStrLn "Server stopped"

ItemStore_client.cpp

#include <iostream>
#include <chrono>
#include "gen-cpp/ItemStore.h"

#include <transport/TSocket.h>
#include <transport/TBufferTransports.h>
#include <protocol/TBinaryProtocol.h>

using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;

using namespace test;
using namespace std;

#define TIME_INIT  std::chrono::_V2::steady_clock::time_point start, stop; \
                   std::chrono::duration<long long int, std::ratio<1ll, 1000000000ll> > duration;
#define TIME_START start = std::chrono::steady_clock::now(); 
#define TIME_END   duration = std::chrono::steady_clock::now() - start; \
                   std::cout << chrono::duration <double, std::milli> (duration).count() << " ms" << std::endl;

int main(int argc, char **argv) {

    boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
    boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
    boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));

    ItemStoreClient server(protocol);
    transport->open();

    TIME_INIT

    long pings = 100;
    cout << "Sending " << pings << " pings" << endl;
    TIME_START
    for(auto i = 0 ; i< pings ; ++i)
        server.ping();
    TIME_END


    long vectorSize = 1000000;

    cout << "Transfering " << vectorSize << " size vector" << endl;
    std::vector<int> v;
    TIME_START
    server.getVector(v, vectorSize);
    TIME_END
    cout << "Recieved: " << v.size()*sizeof(int) / 1024.0 << " kB" << endl;


    long itemsSize = 100000;

    cout << "Transfering " << itemsSize << " items from server" << endl;
    ItemPack items;
    TIME_START
    server.getItems(items, "test", itemsSize);
    TIME_END


    cout << "Transfering " << itemsSize << " items to server" << endl;
    TIME_START
    server.setItems(items);
    TIME_END

    transport->close();

    return 0;
}

ItemStore_server.cpp

#include "gen-cpp/ItemStore.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>

#include <map>
#include <vector>

using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;


using namespace test;
using boost::shared_ptr;

class ItemStoreHandler : virtual public ItemStoreIf {
  public:
    ItemStoreHandler() {
    }

    void ping() {
        // printf("ping\n");
    }

    void getItems(ItemPack& _return, const std::string& name, const int32_t count) {

        std::vector <Item> items;
        std::map<int, Item> mappers;

        for(auto i = 0 ; i < count ; ++i){
            std::vector<int> coordinates;
            for(auto c = i ; c< 100 ; ++c)
                coordinates.push_back(c);

            Item item;
            item.__set_name(name);
            item.__set_coordinates(coordinates);

            items.push_back(item);
            mappers[i] = item;
        }

        _return.__set_items(items);
        _return.__set_mappers(mappers);
        printf("getItems\n");
    }

    bool setItems(const ItemPack& items) {
        printf("setItems\n");
        return true;
    }

    void getVector(std::vector<int32_t> & _return, const int32_t count) {
        for(auto i = 0 ; i < count ; ++i)
            _return.push_back(i);
        printf("getVector\n");
    }
};

int main(int argc, char **argv) {
    int port = 9090;
    shared_ptr<ItemStoreHandler> handler(new ItemStoreHandler());
    shared_ptr<TProcessor> processor(new ItemStoreProcessor(handler));
    shared_ptr<TServerTransport> serverTransport(new TServerSocket(port));
    shared_ptr<TTransportFactory> transportFactory(new TBufferedTransportFactory());
    shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());

    TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
    server.serve();
    return 0;
}

メイクファイル

GEN_SRC := gen-cpp/ItemStore.cpp gen-cpp/performance_constants.cpp gen-cpp/performance_types.cpp
GEN_OBJ := $(patsubst %.cpp,%.o, $(GEN_SRC))

THRIFT_DIR := /usr/local/include/thrift
BOOST_DIR := /usr/local/include

INC := -I$(THRIFT_DIR) -I$(BOOST_DIR)

.PHONY: all clean

all:   ItemStore_server ItemStore_client

%.o: %.cpp
    $(CXX) --std=c++11 -Wall -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H $(INC) -c $< -o $@

ItemStore_server: ItemStore_server.o $(GEN_OBJ) 
    $(CXX) $^ -o $@ -L/usr/local/lib -lthrift -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H

ItemStore_client: ItemStore_client.o $(GEN_OBJ)
    $(CXX) $^ -o $@ -L/usr/local/lib -lthrift -DHAVE_INTTYPES_H -DHAVE_NETINET_IN_H

clean:
    $(RM) *.o ItemStore_server ItemStore_client

コンパイルして実行

私はファイルを生成します(ここで入手可能なthrift 0.9を使用):

$ thrift --gen cpp performance.thrift
$ thrift --gen hs performance.thrift

でコンパイル

$ make
$ ghc Main.hs gen-hs/ItemStore_Client.hs gen-hs/ItemStore.hs gen-hs/ItemStore_Iface.hs gen-hs/Performance_Consts.hs gen-hs/Performance_Types.hs -Wall -O2

Haskell テストを実行します。

$ ./Main& 
$ ./ItemStore_client

C++ テストを実行します。

$ ./ItemStore_server&
$ ./ItemStore_client

各テストの後にサーバーを強制終了することを忘れないでください

アップデート

の代わりgetVectorに使用するメソッドを編集しましたが、それでも効果はありませんVector.generateVector.fromList

更新 2

@MdxBhmt の提案により、getItems次のように関数をテストしました。

getItems _ mtname mtsize = do let size = i32toi $! fromJust mtsize
                                  item i = Item mtname (Just $!  Vector.enumFromN (i::Int32) (100- (fromIntegral i)))
                                  itemsv = Vector.map item  $ Vector.enumFromN 0  (size-1)
                                  itemPack = ItemPack (Just itemsv) Nothing 
                              putStrLn "getItems"
                              return itemPack

これは厳密であり、私の元の実装に基づいて、Vector 生成とその代替手段が改善されました。

getItems _ mtname mtsize = do let size = i32toi $ fromJust mtsize
                                  item i = Item mtname (Just $ Vector.fromList $ map itoi32 [i..100])
                                  items = map item [0..(size-1)]
                                  itemsv = Vector.fromList items 
                                  itemPack = ItemPack (Just itemsv) Nothing
                              putStrLn "getItems"
                              return itemPack

HashMap が送信されていないことに注意してください。最初のバージョンの時間は 12338.2 ミリ秒で、2 番目のバージョンは 11698.7 ミリ秒で、スピードアップはありません :(

アップデート 3

Thrift Jiraに問題を報告しました

abhinavによるアップデート4

これは完全に非科学的ですが、GHC 7.8.3 と Thrift 0.9.2 および @MdxBhmt のバージョンの を使用するgetItemsと、不一致が大幅に減少します。

C++ server and C++ client:

Sending 100 pings:                     8.56 ms
Transferring 1000000 size vector:      137.97 ms
Recieved:                              3906.25 kB
Transferring 100000 items from server: 467.78 ms
Transferring 100000 items to server:   207.59 ms

Haskell server and C++ client:

Sending 100 pings:                     24.95 ms
Recieved:                              3906.25 kB
Transferring 1000000 size vector:      378.60 ms
Transferring 100000 items from server: 233.74 ms
Transferring 100000 items to server:   913.07 ms

複数の実行が実行され、そのたびにサーバーが再起動されました。結果は再現可能です。

元の質問のソース コード (@MdxBhmt のgetItems実装を使用) は、そのままではコンパイルされないことに注意してください。次の変更を行う必要があります。

getItems _ mtname mtsize = do let size = i32toi $! fromJust mtsize
                                  item i = Item mtname (Just $!  Vector.enumFromN (i::Int32) (100- (fromIntegral i)))
                                  itemsv = Vector.map item  $ Vector.enumFromN 0  (size-1)
                                  itemPack = ItemPack (Just itemsv) Nothing 
                              putStrLn "getItems"
                              return itemPack

getVector _ mtsize       = do putStrLn "getVector"
                              let size = i32toi $ fromJust mtsize
                              return $ Vector.generate size itoi32
4

5 に答える 5

12

これは、user13251 の言うこととかなり一致しています。thrift の haskell 実装は、多数の小さな読み取りを意味します。

EG: Thirft.Protocol.Binary で

readI32 p = do
    bs <- tReadAll (getTransport p) 4
    return $ Data.Binary.decode bs

他の奇妙なビットは無視して、今はそれだけに集中しましょう。これは、「32 ビットの int を読み取るには、トランスポートから 4 バイトを読み取り、この遅延バイト文字列をデコードする」ことを意味します。

transport メソッドは、遅延バイト文字列 hGet を使用して正確に 4 バイトを読み取ります。hGet は次のことを行います: 4 バイトのバッファを割り当て、hGetBuf を使用してこのバッファを埋めます。hGetBuf は、ハンドルがどのように初期化されたかによって、内部バッファーを使用している可能性があります。

そのため、バッファリングが発生する可能性があります。それでも、これは Haskell の Thrift が各整数の読み取り/デコード サイクルを個別に実行していることを意味します。毎回小さなメモリ バッファを割り当てます。痛い!

より大きなバイト文字列の読み取りを実行するように Thrift ライブラリを変更しない限り、これを修正する方法は実際にはありません。

次に、thrift の実装には他にも奇妙な点があります。メソッドの構造にクラスを使用することです。それらは似ており、メソッドの構造のように機能し、メソッドの構造として実装されることもありますが、そのように扱われるべきではありません。「Existential Typeclass」アンチパターンを参照してください。

テスト実装の奇妙な部分:

  • Int の配列のみを生成してすぐに Int32 に変更し、Int32 の Vector にすぐにパックします。ベクトルをすぐに生成するだけで十分かつ高速です。

ただし、これはパフォーマンスの問題の主な原因ではないと思います。

于 2013-10-22T23:16:36.393 に答える
12

Haskell プロファイリング メソッドを調べて、プログラムが使用/割り当てているリソースとその場所を確認する必要があります。

Real World Haskellでのプロファイリングに関する章は、良い出発点です。

于 2013-10-22T16:29:01.753 に答える
10

Haskellサーバーでのバッファリングへの言及は見当たりません。C++ では、バッファリングしない場合、ベクトル/リスト要素ごとに 1 つのシステム コールが発生します。Haskellサーバーでも同じことが起こっていると思います。

Haskell でバッファリングされたトランスポートが直接表示されません。実験として、フレーム化されたトランスポートを使用するようにクライアントとサーバーの両方を変更したい場合があります。Haskell にはフレーム化されたトランスポートがあり、バッファリングされています。これにより、ワイヤ レイアウトが変更されることに注意してください。

別の実験として、C++ のバッファリングをオフにして、パフォーマンスの数値が同等かどうかを確認することができます。

于 2013-10-22T22:01:59.457 に答える
6

使用している基本的なリサイクル サーバーの Haskell 実装は、内部でスレッド化を使用していますが、複数のコアを使用するようにコンパイルしていません。

複数のコアを使用してテストを再度行うには、Haskell プログラムをコンパイルするためのコマンド ラインを変更して と を含め-rtsopts-threaded最終的なバイナリを のよう./Main -N4 &に実行します。ここで、4 は使用するコアの数です。

于 2013-10-22T11:43:23.563 に答える