rem*_*ezx 42 c++ performance networking haskell thrift
我正在构建一个包含两个组件的应用程序 - 用Haskell编写的服务器和用Qt(C++)编写的客户端.我正在使用节俭来传达它们,我想知道它为什么这么慢.
我进行了性能测试,这是我机器上的结果
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
Run Code Online (Sandbox Code Playgroud)
为什么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)
}
Run Code Online (Sandbox Code Playgroud)
{-# 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"
Run Code Online (Sandbox Code Playgroud)
#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;
}
Run Code Online (Sandbox Code Playgroud)
#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;
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我生成文件(使用节俭0.9可在这里用):
$ thrift --gen cpp performance.thrift
$ thrift --gen hs performance.thrift
Run Code Online (Sandbox Code Playgroud)
编译
$ 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
Run Code Online (Sandbox Code Playgroud)
运行Haskell测试:
$ ./Main&
$ ./ItemStore_client
Run Code Online (Sandbox Code Playgroud)
运行C++测试:
$ ./ItemStore_server&
$ ./ItemStore_client
Run Code Online (Sandbox Code Playgroud)
记得在每次测试后杀死服务器
编辑getVector
使用的方法Vector.generate
代替Vector.fromList
,但仍然没有效果
由于@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
Run Code Online (Sandbox Code Playgroud)
这是严格的,并根据我原来的实现改进了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
Run Code Online (Sandbox Code Playgroud)
请注意,没有发送HashMap.第一个版本给出时间12338.2毫秒,第二个版本给出11698.7毫秒,没有加速:(
我向Thrift Jira报道了一个问题
这完全不科学,但使用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
Run Code Online (Sandbox Code Playgroud)
执行了多次执行,每次都重新启动服务器.结果是可重复的.
请注意,原始问题的源代码(使用@ 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
Run Code Online (Sandbox Code Playgroud)
Mdx*_*hmt 27
每个人都指出,罪魁祸首是节俭库,但我会专注于你的代码(我可以帮助你获得一些速度)
使用代码的简化版本,您可以在其中计算itemsv
:
testfunc mtsize = itemsv
where size = i32toi $ fromJust mtsize
item i = Item (Just $ Vector.fromList $ map itoi32 [i..100])
items = map item [0..(size-1)]
itemsv = Vector.fromList items
Run Code Online (Sandbox Code Playgroud)
首先,您要创建许多中间数据item i
.由于懒惰,那些小而快速计算的向量会成为数据的延迟,当我们可以立即获得它们时.
有2个仔细放置$!
,代表严格的评价:
item i = Item (Just $! Vector.fromList $! map itoi32 [i..100])
Run Code Online (Sandbox Code Playgroud)
将使运行时间减少25%(对于大小1e5和1e6).
但是这里存在一个更有问题的模式:您生成一个列表以将其转换为向量,而不是直接构建向量.
查看最后两行,创建一个列表 - >映射一个函数 - >转换为一个向量.
好吧,矢量与列表非常相似,你可以做类似的事情!所以你必须在它上面生成一个vector - > vector.map并完成.不再需要将列表转换为向量,并且向量上的maping通常比列表更快!
所以你可以摆脱items
并重写以下内容itemsv
:
itemsv = Vector.map item $ Vector.enumFromN 0 (size-1)
Run Code Online (Sandbox Code Playgroud)
重新应用相同的逻辑item i
,我们消除所有列表.
testfunc3 mtsize = itemsv
where
size = i32toi $! fromJust mtsize
item i = Item (Just $! Vector.enumFromN (i::Int32) (100- (fromIntegral i)))
itemsv = Vector.map item $ Vector.enumFromN 0 (size-1)
Run Code Online (Sandbox Code Playgroud)
这比初始运行时减少了50%.
Cor*_*nor 12
这与user13251所说的完全一致:thrift的haskell实现意味着大量的小读取.
EG:在Thirft.Protocol.Binary中
readI32 p = do
bs <- tReadAll (getTransport p) 4
return $ Data.Binary.decode bs
Run Code Online (Sandbox Code Playgroud)
让我们忽略其他奇数位,现在只关注它.这说:"读取32位int:从传输中读取4个字节,然后解码这个懒惰的字节串."
传输方法使用lazy bytestring hGet准确读取4个字节.hGet将执行以下操作:分配4字节的缓冲区,然后使用hGetBuf填充此缓冲区.hGetBuf可能正在使用内部缓冲区,具体取决于Handle的初始化方式.
所以可能会有一些缓冲.即便如此,这意味着hasrell的Thrift正在为每个整数单独执行读/解码循环.每次分配一个小内存缓冲区.哎哟!
如果没有修改Thrift库来执行更大的字节串读取,我真的没有办法解决这个问题.
然后在thrift实现中还有其他奇怪之处:使用类作为方法结构.虽然它们看起来很相似,并且可以像方法结构那样起作用,有时甚至可以作为方法结构实现:它们不应该被视为这样.请参阅"Existential Typeclass"反模式:
测试实现的一个奇怪部分:
虽然,我怀疑,这不是性能问题的主要来源.
小智 10
我没有在Haskell服务器中看到任何对缓冲的引用.在C++中,如果不缓冲,则对每个vector/list元素进行一次系统调用.我怀疑在Haskell服务器中发生了同样的事情.
我没有直接在Haskell中看到缓冲传输.作为实验,您可能希望更改客户端和服务器以使用框架传输.Haskell确实有一个框架式传输,它是缓冲的.请注意,这将改变电线布局.
作为一个单独的实验,您可能希望为C++关闭缓存,并查看性能数字是否具有可比性.
您正在使用的基本thrift服务器的Haskell实现在内部使用线程,但您没有编译它以使用多个内核.
要再次做了测试使用多个内核,改变你的命令行编译Haskell的程序,包括-rtsopts
和-threaded
,然后运行最终的二进制像./Main -N4 &
,其中4是内核使用的数量.
归档时间: |
|
查看次数: |
7284 次 |
最近记录: |