Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Two physical packets per sf::Packet?  (Read 9983 times)

0 Members and 1 Guest are viewing this topic.

P@u1

  • Jr. Member
  • **
  • Posts: 83
    • View Profile
Two physical packets per sf::Packet?
« on: October 22, 2011, 03:56:01 pm »
Hi everyone,

I just read some of the network source code and recognized a possible problem.

TcpSocket::Send(Packet& packet) looks like this:
Code: [Select]

TcpSocket::Send(Packet& packet)
//...
// First send the packet size
    Uint32 packetSize = htonl(static_cast<unsigned long>(size));
    Status status = Send(reinterpret_cast<const char*>(&packetSize), sizeof(packetSize));

//...
// Send the packet data
    if (packetSize > 0)
    {
        return Send(data, size);
    }
//...


And you also disable nagle-algorithm, so that if I understand it right every call to send directly sends a physical packet over the network.
You are calling send 2 times in a row with very low time between the calls.
If that really yields 2 phyiscal packets (and it should do as nagle algorithm is off) with tcp/ip-header then this is quite a big overhead, don't you think so?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #1 on: October 22, 2011, 04:07:02 pm »
That's right, but how could I avoid that? Concatenating all the data in a single buffer would require a dynamic memory allocation, which is even worse.

Anyway, it's a potential problem but its impact would have to be measured precisely before complicating the implementation ;)
Laurent Gomila - SFML developer

P@u1

  • Jr. Member
  • **
  • Posts: 83
    • View Profile
Two physical packets per sf::Packet?
« Reply #2 on: October 22, 2011, 04:10:01 pm »
I don't agree that a dynamic memory allocation is worse than using an additoinal packet.

Bandwith is VERY rare while cpu and ram are VERY fast.
I would sacrifice a lot of cycles to gain additional bandwith :-)
Maybe you could reuse one or more buffers (maybe hold them in a pool)?

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #3 on: October 22, 2011, 04:56:56 pm »
Like I said, I won't do anything until someone proves me that he really has performances problems with this ;)
Laurent Gomila - SFML developer

P@u1

  • Jr. Member
  • **
  • Posts: 83
    • View Profile
Two physical packets per sf::Packet?
« Reply #4 on: October 22, 2011, 11:46:59 pm »
What kind of prove do you need?

If you are only talking about having perfomance problems with tcp at all, this would be no problem.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #5 on: October 23, 2011, 10:15:20 am »
Quote
What kind of prove do you need?

Something like "I have a problem with that, look".
"There might be a problem with that" is not enough, especially if it requires making the implementation uglier and more complicated.
Laurent Gomila - SFML developer

P@u1

  • Jr. Member
  • **
  • Posts: 83
    • View Profile
Two physical packets per sf::Packet?
« Reply #6 on: October 23, 2011, 01:44:34 pm »
I did some tests:

Code: [Select]
int main(int argc, char** argv)
{
sf::TcpListener listener;
sf::Socket::Status status = listener.Listen(3456);
alwaysAssert(status == sf::Socket::Done);
sf::Thread thread([&]()
{
sf::TcpSocket socket;
sf::Socket::Status status = listener.Accept(socket);
alwaysAssert(status == sf::Socket::Done);
std::size_t receivedLen;
char buffer[10000];
while(true)
{
socket.Receive(buffer, sizeof(buffer), receivedLen);
}
});
thread.Launch();

sf::TcpSocket socket;
status = socket.Connect("127.0.0.1", 3456);
alwaysAssert(status == sf::Socket::Done);

sf::Packet toSend;
toSend << "hello";
const char * const msg = "1234hello";
unsigned int len = strlen(msg);

for(int i = 0; i < 20; ++i)
{
sf::Clock clock;
for(int i = 0; i < 100000; ++i)
{
status = socket.Send(toSend);
alwaysAssert(status == sf::Socket::Done);
}
unsigned int elapsed = clock.GetElapsedTime();
cout << "Duration with packet: " << elapsed << " ms" << endl;

//give the network time to stabilize
sf::Sleep(10000);

clock.Reset();

for(int i = 0; i < 100000; ++i)
{
status = socket.Send(msg, len);
alwaysAssert(status == sf::Socket::Done);
}
elapsed = clock.GetElapsedTime();
cout << "Duration without packet: " << elapsed << " ms" << endl;

//give the network time to stabilize
sf::Sleep(10000);
}

thread.Terminate();

return 0;
}


I forwarded the output to a textfile and started it in release mode.
The output is this:
Quote
Duration with packet: 17732 ms
Duration without packet: 8257 ms
Duration with packet: 17495 ms
Duration without packet: 9239 ms
Duration with packet: 17827 ms
Duration without packet: 8324 ms
Duration with packet: 16481 ms
Duration without packet: 8103 ms
Duration with packet: 16720 ms
Duration without packet: 8155 ms
Duration with packet: 16290 ms
Duration without packet: 8158 ms
Duration with packet: 16321 ms
Duration without packet: 8132 ms
Duration with packet: 16337 ms
Duration without packet: 8162 ms
Duration with packet: 16294 ms
Duration without packet: 8168 ms
Duration with packet: 16403 ms
Duration without packet: 8187 ms
Duration with packet: 16337 ms
Duration without packet: 8132 ms
Duration with packet: 16388 ms
Duration without packet: 8074 ms
Duration with packet: 16714 ms
Duration without packet: 8167 ms
Duration with packet: 16321 ms
Duration without packet: 8533 ms
Duration with packet: 16336 ms
Duration without packet: 8184 ms
Duration with packet: 16331 ms
Duration without packet: 8140 ms
Duration with packet: 16310 ms
Duration without packet: 8157 ms
Duration with packet: 16260 ms
Duration without packet: 8357 ms
Duration with packet: 16418 ms
Duration without packet: 8129 ms
Duration with packet: 16191 ms
Duration without packet: 8080 ms


This shows that when testing locally with very small messages, using sf::Packet takes about twice the time then sending without sf::Packet.
I'm quite sure that it's because of 2 packets being sent instead of one.

Does this convince you, or do I need to do more? ^^

Here is also a possible solution:
When sending a packet you first make sure that the vector is large enough to store 4 additional bytes (for the size).
Then you use std::memmov to move the data 4 bytes to the right.
Then you write the size at the first 4 bytes.
Then you use the Send overload which takes raw bytes.
Then you use memmove again to revert the change.

I have 2 more questions:
- Why are you always using resize when something is written to a packet? If the standard-library implementation allocates exactly the size you request and no more then you need to reallocate for every write. Maybe it would be better to allocate more than is needed.
- When having an non-blocking socket and doing a sent, must the buffer which is sent then be unchanged until the operation is completed?
For example if I do:
Code: [Select]
char buffer[1024];
socket.Send(buffer, sizeof(buffer));
buffer[1023] = 'a';

Can the change to the buffer then affect what actually is sent?
With boost::asio this can happen, but it also tells you when the operation is completed.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #7 on: October 23, 2011, 02:02:25 pm »
Hmm... ok, you should add an issue to the task tracker, and maybe I'll take a look at it for SFML 2.1 :P

Quote
Why are you always using resize when something is written to a packet? If the standard-library implementation allocates exactly the size you request and no more then you need to reallocate for every write. Maybe it would be better to allocate more than is needed.

I don't optimize when there's no need to. Do another benchmark if you want me to change this ;)

Quote
When having an non-blocking socket and doing a sent, must the buffer which is sent then be unchanged until the operation is completed?

The Send function is blocking even if the socket is not. There's no asynchronous function in SFML, which would do something in the background and notify you when it's finished.
Laurent Gomila - SFML developer

P@u1

  • Jr. Member
  • **
  • Posts: 83
    • View Profile
Two physical packets per sf::Packet?
« Reply #8 on: October 23, 2011, 02:21:27 pm »
Quote from: "Laurent"
Concatenating all the data in a single buffer would require a dynamic memory allocation, which is even worse.

Quote from: "Laurent"
I don't optimize when there's no need to. Do another benchmark if you want me to change this ;)


First you a fearing dynamic allocations and then you don't care :-)
But it's not a problem. I never had problems with this, it was just a suggestion.

Quote from: "Laurent"

Hmm... ok, you should add an issue to the task tracker, and maybe I'll take a look at it for SFML 2.1 :P


Done.

nitrix

  • Newbie
  • *
  • Posts: 27
    • View Profile
Two physical packets per sf::Packet?
« Reply #9 on: December 07, 2011, 09:07:43 am »
Quote
That's right, but how could I avoid that? Concatenating all the data in a single buffer would require a dynamic memory allocation, which is even worse.


Or you can always keep an Uint32 allocated in the buffer right before the data and write the packet size to it when it's being sent.

Okay it introduces a bit of pointer arithmetics but nothing aweful, really.

This way you don't do any dynamic allocations, the only downside is that every Packet object is bigger of an int. (I don't think this is a big concern)

I just submitted a pull request on GitHub with my fixes. See how you're happy with it, I'd really need this implemented as well.

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #10 on: December 07, 2011, 09:21:17 am »
Quote
Or you can always keep an Uint32 allocated in the buffer right before the data and write the packet size to it when it's being sent.

This is kind of ugly too because the packet directly manages an implementation detail of the TCP socket.
This tight coupling between classes in the implementation details makes the code more difficult to maintain.
Laurent Gomila - SFML developer

nitrix

  • Newbie
  • *
  • Posts: 27
    • View Profile
Two physical packets per sf::Packet?
« Reply #11 on: December 07, 2011, 09:37:40 am »
Yeah, I have to admit it's an hack-ish way to do things. Tho, that's how major networking libraries implement it (ACE is the best library I'm aware of).

They used the have only one class, but now it's cut into 3 parts, very dependant on each-others. You might hate it, but it's a HUGE optimisation.

-----------------

You're talented with graphics, I give you that. But I've been working with sockets and networks for a long time too. This is really something you would want fixed if you were to code a game with SFML.

The only other ways I can think is having TcpSocket allocate a copy of the packet and prepend the size before copying the actual packet,

or

leave it bugged (please don't).

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #12 on: December 07, 2011, 10:13:35 am »
My message was just an immediate comment to your proposed solution, it doesn't mean that I have a better one, or that I prefer to leave the code bugged ;)

This will probably be the best solution, but I can't blindly merge your pull request without first thinking about this problem myself.

I'm currently very busy with the new graphics API, and after that I plan to release SFML 2.0, that's why this issue is tagged "2.x" ;)
Laurent Gomila - SFML developer

nitrix

  • Newbie
  • *
  • Posts: 27
    • View Profile
Two physical packets per sf::Packet?
« Reply #13 on: December 07, 2011, 10:28:51 am »
Well it doesn't break the API anyway so I can use the network library for now and hope for a patch in the future :)

I just wanted to help out, it seems like there's just so many things going on this project at the same time.

I can predict a bright future for SFML. So far everything looks well though out, issues are considered one by one, it's just.. wow.

When I'm done with my game, I'll definitively donate to SFML and pay you a beer or something ahahah xD

Cheers!

Laurent

  • Administrator
  • Hero Member
  • *****
  • Posts: 32498
    • View Profile
    • SFML's website
    • Email
Two physical packets per sf::Packet?
« Reply #14 on: December 07, 2011, 10:38:42 am »
Hehe, thanks :)

I've been stuck for a long time on the new graphics API, but now that it's almost done I'll be able to get back to a normal workflow, and start to work on all the smaller 2.x tasks.
Laurent Gomila - SFML developer