I did it! It was indeed a bug in the .net framework (not in F#). I tried serializing to a MemoryStream instead of a NetworkStream, and then writing the bytes to the NetworkStream. It worked. It is fast.

This works:

1
2
3
4
5
6
 let bf = new BinaryFormatter() in
  let ms = new MemoryStream () in
  bf.Serialize(ms, box pacchettoIniziale);
  let bytes = ms.GetBuffer () in
  ns.Write(bytes, 0, System.Convert.ToInt32(ms.Length));
  ns.Flush()

This does not:

1
2
3
 let bf = new BinaryFormatter() in
  bf.Serialize(ns, box pacchettoIniziale);
  ns.Flush()

(also note that, in my benchmark application, the flush command is missing)

Now only one question remains: why does my game crash on Deserialize if compiled with optimizations enabled?

However thank you very much for your support. Without your second test application, I would not have discovered the solution. :)

Regards
Maurizio

By on 8/7/2007 10:01 AM ()

Good question!

I just tried the following code on my laptop and got 430 communications/sec. It's a #light version of the above with the SDL.getTicks call replaced.

I'll look into this a bit more and encourage other people to do the same, given the huge discrepancies we're seeing.

don

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
 

open System.Runtime.Serialization.Formatters.Binary
open System.Net.Sockets


let rec mainLoopServer ns (time0:System.DateTime) frameCount = 
    let time1 = System.DateTime.Now
    let diff = time1 - time0
    let fps = float frameCount / float diff.TotalSeconds
    printfn "frames per second = %f" fps;

    let formatter = new BinaryFormatter() 
    let pack  = unbox<int*int>(formatter.Deserialize(ns)) 
    formatter.Serialize(ns,  box pack);
    mainLoopServer ns time0 (frameCount + 1)


let initServer port = 
    let tcpL = new TcpListener(System.Net.IPAddress.Any, port) 
    tcpL.Start()
    printfn "server: started listening to port %d." port;

    let  ns = 
        let clientSocDn = tcpL.AcceptSocket ()  (* blocking *) 
        printfn "server: client accepted";
        new NetworkStream(clientSocDn) 

    mainLoopServer ns System.DateTime.Now 0 

let rec mainLoopClient ns = 

    let formatter = new BinaryFormatter() 

    let pack = (3,4)
    formatter.Serialize(ns,  box pack); 
    let pack = unbox<int*int> (formatter.Deserialize(ns))
    
    mainLoopClient ns


let initClient serveraddr port = 
    
    let socketForServer = new TcpClient(serveraddr, port) 
        
    printfn "client: connected to server at address %s and port %d." serveraddr port; 
    let ns = socketForServer.GetStream() 

    mainLoopClient ns
      
    
let _ = 

    let server = ref false 
    let serveraddr = ref "localhost" 
    let port = ref 5000 
    let arguments = [ "-server", Arg.Set server, "Whether to call this program as a server or a client.";
              "-serveraddr", Arg.String (fun x -> serveraddr := x), "The address of the server. Examples: 192.168.1.2, localhost.";
              "-port", Arg.Int (fun x -> port := x), "The TCP port to use. Default is 5000."] 

    let anonFun s =
        print_endline "No anonymous arguments allowed." 
    Arg.parse arguments anonFun "Usage:"

    if !server then initServer (!port)
    else initClient (!serveraddr) (!port)
    
By on 8/6/2007 1:31 PM ()

This one gave me 9000/s when "bytes" is set to "true" (i.e. transacting single bytes). Of course OCaml will do better here too - I haven't tried that. It also gave me 1650/s when "bytes" is set to false. Here we only report the result every 10000 iterations, indicating there is some small overhead to the DateTime.Now calls etc. BTW my machine is a dual-core Intel laptop, Dell Latitude 420.

Of course this doesn't explain why you're getting 3 communications/sec on your version of the micro benchmark, nor poor performance on the overall program.

It's possible binary serialization is at fault for the poor performance on your larger program. It would be interesting to know the overall number of bytes being sent to see how much greater it is in the case of .NET. The .NET binary serialization format is considerably fatter than the OCaml binary serialization format, because it sends type information, and rechecks that type information upon reading - the OCaml one does not. There can be a difference of 10x in the sizes of the formats.

If that's the problem then replacing binary serialization is a good way to go, though you will still have to make sure the actual number of bytes being sent is low enough. Also, manual serialization may not necessarily improve performance unless you preserve the "graph" of data - both .NET and OCaml binary serialization preserve the underlying object graph should there be any internal sharing in that graph.

regards

don

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
 

#light

open System.Runtime.Serialization.Formatters.Binary
open System.Net.Sockets
open System.IO

let bytes = false

let rec mainLoopServer (ns: #Stream) (time0:System.DateTime) frameCount = 
    if frameCount % 10000 = 0 then 
      let time1 = System.DateTime.Now
      let diff = time1 - time0
      let fps = float frameCount / float diff.TotalSeconds
      printfn "frames per second = %f" fps;

    if bytes then 
        ns.WriteByte 0uy
        ns.ReadByte() |> ignore
    else
        let formatter = new BinaryFormatter() 
        let pack  = unbox<int*int>(formatter.Deserialize(ns)) 
        formatter.Serialize(ns,  box pack);
    mainLoopServer ns time0 (frameCount + 1)


let initServer port = 
    let tcpL = new TcpListener(System.Net.IPAddress.Any, port) 
    tcpL.Start()
    printfn "server: started listening to port %d." port;

    let  ns = 
        let clientSocDn = tcpL.AcceptSocket ()  (* blocking *) 
        printfn "server: client accepted";
        new NetworkStream(clientSocDn) 

    mainLoopServer ns System.DateTime.Now 0 

let rec mainLoopClient (ns: #Stream) = 

    let formatter = new BinaryFormatter() 

    if bytes then 
        ns.ReadByte() |> ignore
        ns.WriteByte 0uy
    else 
        let pack = (3,4)
        formatter.Serialize(ns,  box pack); 
        let pack = unbox<int*int> (formatter.Deserialize(ns))
        ()
    
    mainLoopClient ns


let initClient serveraddr port = 
    
    let socketForServer = new TcpClient(serveraddr, port) 
        
    printfn "client: connected to server at address %s and port %d." serveraddr port; 
    let ns = socketForServer.GetStream() 

    mainLoopClient ns
      
    
let _ = 

    let server = ref false 
    let serveraddr = ref "localhost" 
    let port = ref 5000 
    let arguments = [ "-server", Arg.Set server, "Whether to call this program as a server or a client.";
              "-serveraddr", Arg.String (fun x -> serveraddr := x), "The address of the server. Examples: 192.168.1.2, localhost.";
              "-port", Arg.Int (fun x -> port := x), "The TCP port to use. Default is 5000."] 

    let anonFun s =
        print_endline "No anonymous arguments allowed." 
    Arg.parse arguments anonFun "Usage:"

    if !server then initServer (!port)
    else initClient (!serveraddr) (!port)


// Writing single bytes: 7400    
// Writing serialized bytes: 1400    
// Writing serialized bytes, with time test every iteration: 400    
By on 8/6/2007 2:06 PM ()

Don,

Thank you for your attempt. Unfortunately it seems I need more help :).

You say you had both processes running on the same machine? But that is fast for me too. What is slow is running server and client on different machines. In this case I get 3 frames per second. Sorry for not mentioning this in my first post. (By the way, this does not seem to be a problem in my LAN, because it does not happen with ocaml, and because I tried two different LANS.)

Now to the result of your benchmarks:

When I run your _first_ benchmark application on two different machines in my LAN, I get 3.1 round-trips per second, which is no good news.

When I run your _second_ benchmark application on two different machines:

with bytes = true, I get 4100 frames per second, which is fast. That might be a clue.

with bytes = false, I get fps = 0 and then the program seems to hang for a minute. This means that frame #10000 never arrives. Which probably means we are actually getting 3 fps, as before.

As for the size of packet in my real game: it is small. I don't know how to measure it, however it contains two dozen floats, a couple of strings of about 20 characters, and a dozen discriminated unions. But that does not seem important since the problem is happening in the benchmark too, which only exchanges 2 floats.

Another thing that mght be a clue is that I have to compile my game with optimizations disabled (-Ooff). If I try to run it with any other optimization level, it crashes when I call Deserialize. I did not try the benchmark application with optimizations enabled, to see if it too crashes. Maybe I should.

What do you suggest to try next? I was wondering... is it possible to serialize the packet onto a string (as opposed to a networkStream), and then get the bytes of the string, and send them with NetworkStrem.WriteBytes? thanks again.

Maurizio

By on 8/6/2007 11:25 PM ()
IntelliFactory Offices Copyright (c) 2011-2012 IntelliFactory. All rights reserved.
Home | Products | Consulting | Trainings | Blogs | Jobs | Contact Us | Terms of Use | Privacy Policy | Cookie Policy
Built with WebSharper