2

Some context to my problem:

I need to establish an inter-process communication using C++ and sockets and I picked NNG library for that along with nngpp c++ wrapper. I need to use push/pull protocol so no contexts handling is available to me. I wrote some code based on raw example from nngpp demo. The difference here is that, by using push/pull protocol I split this into two separate programs. One for sending and one for receiving.

Problem descripion:

I need to receive let's say a thousand or more messages per second. For now, all messages are captured only when I send about 50/s. That is way too slow and I do believe it can be done faster. The faster I send, the more I lose. At the moment, when sending 1000msg/s I lose about 150 msgs.

Some words about the code

The code may be in C++17 standard. It is written in object-oriented manner so in the end I want to have a class with "receive" method that would simply give me the received messages. For now, I just print the results on screen. Below, I supply some parts of the project with descriptions:

NOTE msgItem is a struct like that:

  struct msgItem {
    nng::aio aio;
    nng::msg msg;
    nng::socket_view itemSock;
    explicit msgItem(nng::socket_view sock) : itemSock(sock) {}
  };

And it is taken from example mentioned above. Callback function that is executed when message is received by one of the aio's (callback is passed in constructor of aio object). It aims at checking whether everything was ok with transmission, retrieving my Payload (just string for now) and passing it to queue while a flag is set. Then I want to print those messages from the queue using separate thread.

void ReceiverBase<Payload>::aioCallback(void *arg) try { 
msgItem *msgItem = (struct msgItem *)arg;
  Payload retMsg{};
 
  auto result = msgItem->aio.result();
  if (result != nng::error::success) {
    throw nng::exception(result);
  }
  //Here we extract the message
  auto msg = msgItem->aio.release_msg();
  auto const *data = static_cast<typename Payload::value_type *>(msg.body().data());
  auto const count = msg.body().size()/sizeof(typename Payload::value_type);
  std::copy(data, data + count, std::back_inserter(retMsg));
  {
      std::lock_guard<std::mutex> lk(m_msgMx);
      newMessageFlag = true;
      m_messageQueue.push(std::move(retMsg));
  }
  msgItem->itemSock.recv(msgItem->aio);

} catch (const nng::exception &e) {
  fprintf(stderr, "server_cb: %s: %s\n", e.who(), e.what());
} catch (...) {
  fprintf(stderr, "server_cb: unknown exception\n");
}

Separate thread for listening to the flag change and printing. While loop at the end is for continuous work of the program. I use msgCounter to count successful message receival.

void ReceiverBase<Payload>::start() {
  auto listenerLambda =  [](){
       std::string temp;
   while (true) {
       std::lock_guard<std::mutex> lg(m_msgMx);
       if(newMessageFlag) {
         temp = std::move(m_messageQueue.front());
         m_messageQueue.pop();
         ++msgCounter;
         std::cout << msgCounter << "\n";
         newMessageFlag = false;
         }}};

   std::thread listenerThread (listenerLambda);
  while (true) {
     std::this_thread::sleep_for(std::chrono::microseconds(1));
   }
}

This is my sender application. I tweak the frequency of msg sending by changing the value in std::chrono::miliseconds(val).

int main (int argc, char *argv[])
{
  std::string connection_address{"ipc:///tmp/async_demo1"};
  std::string longMsg{" here normally I have some long test text"};
  std::cout << "Trying connecting sender:";
  StringSender sender(connection_address);
  sender.setupConnection();
  for (int i=0; i<1000; ++i) {
  std::this_thread::sleep_for(std::chrono::milliseconds(3));
  sender.send(longMsg);
  }
}

And this is receiver:

int main (int argc, char *argv[])
{
  std::string connection_address{"ipc:///tmp/async_demo1"};
  std::cout << "Trying connecting receiver:";
  StringReceiver receiver(connection_address);
  receiver.setupConnection(); 
  std::cout<< "Connection set up. \n";
  receiver.start();
  return 0; 
}

Nothing speciall in those two applications as You see. the setup method from StringReciver is something like that:

bool ReceiverBase<Payload>::setupConnection() {
  m_connected = false;
  try {
    for (size_t i = 0; i < m_parallel; ++i) {
      m_msgItems.at(i) = std::make_unique<msgItem>(m_sock);
      m_msgItems.at(i)->aio =
          nng::aio(ReceiverBase::aioCallback, m_msgItems.at(i).get());
    }
    m_sock.listen(m_adress.c_str());
    m_connected = true;
    for (size_t i = 0; i < m_parallel; ++i) {
      m_msgItems.at(i)->itemSock.recv(m_msgItems.at(i)->aio);
    }
  } catch (const nng::exception &e) {
    printf("%s: %s\n", e.who(), e.what());
  }
 
  return m_connected;
}

Do You have any suggestions why the performance is so low? Do I use lock_guards properly here? What I want them to do is basically lock the flag and queue so only one side has access to it. NOTE: Adding more listeners thread does not affect the performance either way. NOTE2: newMessageFlag is atomic

0 Answers0