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
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)
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
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
Topic tags
- f# × 3705
- websharper × 1897
- compiler × 286
- functional × 201
- ui next × 139
- c# × 121
- classes × 97
- web × 97
- .net × 84
- book × 84
- async × 76
- ui.next × 67
- bug × 54
- core × 49
- website × 49
- server × 45
- parallel × 43
- ui × 43
- enhancement × 41
- parsing × 41
- testing × 41
- trywebsharper × 41
- typescript × 37
- html × 35
- javascript × 35
- owin × 35
- asynchronous × 30
- monad × 28
- ocaml × 28
- tutorial × 27
- warp × 27
- haskell × 26
- sitelet × 25
- linq × 22
- workflows × 22
- wpf × 20
- fpish × 19
- introduction × 19
- silverlight × 19
- sitelets × 19
- monodevelop × 17
- rpc × 17
- suave × 17
- piglets × 16
- collections × 15
- feature request × 15
- jquery × 15
- templates × 15
- getting started × 14
- pipeline × 14
- kendoui × 13
- reactive × 12
- 4.1.0.171 × 11
- monads × 11
- opinion × 10
- 4.0.190.100-rc × 9
- deployment × 9
- fixed × 9
- formlets × 9
- in × 9
- json × 9
- plugin × 9
- proposal × 9
- scheme × 9
- solid × 9
- basics × 8
- concurrent × 8
- highcharts × 8
- how-to × 8
- python × 8
- 4.1.1.175 × 7
- complexity × 7
- documentation × 7
- visual studio × 7
- 4.1.2.178 × 6
- lisp × 6
- real-world × 6
- released in 4.0.192.103-rc × 6
- remoting × 6
- resources × 6
- scala × 6
- websharper ui.next × 6
- workshop × 6
- xaml × 6
- 4.0.193.110 × 5
- 4.2.3.236 × 5
- aspnetmvc × 5
- authentication × 5
- azure × 5
- bootstrap × 5
- conference × 5
- dsl × 5
- formlet × 5
- java × 5
- list × 5
- metaprogramming × 5
- ml × 5
- released in Zafir.4.0.188.91-beta10 × 5
- sql × 5
- visualstudio × 5
- websharper.forms × 5
- zafir × 5
- 4.0.192.106 × 4
- 4.0.195.127 × 4
- 4.1.0.38 × 4
- 4.2.1.86 × 4
- 4.2.6.118 × 4
- css × 4
- example × 4
- extensions × 4
- fsi × 4
- fsx × 4
- html5 × 4
- jqueryui × 4
- lift × 4
- reflection × 4
- remote × 4
- rest × 4
- spa × 4
- teaching × 4
- template × 4
- websocket × 4
- wontfix × 4
- 4.0.196.147 × 3
- 4.1.0.34 × 3
- 4.1.6.207 × 3
- 4.2.1.223-beta × 3
- 4.2.11.258 × 3
- 4.2.4.114 × 3
- 4.2.4.247 × 3
- 4.2.5.115 × 3
- 4.2.6.253 × 3
- 4.2.9.256 × 3
- ajax × 3
- alt.net × 3
- aml × 3
- asp.net mvc × 3
- canvas × 3
- cloudsharper × 3
- compilation × 3
- database × 3
- erlang × 3
- events × 3
- extension × 3
- file upload × 3
- forums × 3
- inline × 3
- issue × 3
- kendo × 3
- macro × 3
- mono × 3
- msbuild × 3
- mvc × 3
- pattern × 3
- piglet × 3
- released in Zafir.4.0.187.90-beta10 × 3
- svg × 3
- type provider × 3
- view × 3
- 4.1.1.64 × 2
- 4.1.5.203 × 2
- 4.1.7.232 × 2
- 4.2.10.257 × 2
- 4.2.3.111 × 2
- 4.2.5.249 × 2
- android × 2
- asp.net × 2
- beginner × 2
- blog × 2
- chart × 2
- client × 2
- client server app × 2
- clojure × 2
- computation expressions × 2
- constructor × 2
- corporate × 2
- courses × 2
- cufp × 2
- d3 × 2
- debugging × 2
- direct × 2
- discriminated union × 2
- docs × 2
- elm × 2
- endpoint × 2
- endpoints × 2
- enterprise × 2
- entity framework × 2
- event × 2
- f# interactive × 2
- fable × 2
- flowlet × 2
- formdata × 2
- forms × 2
- fsc × 2
- google maps × 2
- hosting × 2
- http × 2
- https × 2
- iis 8.0 × 2
- install × 2
- interactive × 2
- interface × 2
- iphone × 2
- iteratee × 2
- jobs × 2
- jquery mobile × 2
- keynote × 2
- lens × 2
- lenses × 2
- linux × 2
- listmodel × 2
- mac × 2
- numeric × 2
- oauth × 2
- obfuscation × 2
- offline × 2
- oop × 2
- osx × 2
- packaging × 2
- pattern matching × 2
- performance × 2
- pipelines × 2
- q&a × 2
- quotation × 2
- reference × 2
- released in Zafir.4.0.185.88-beta10 × 2
- rx × 2
- script × 2
- security × 2
- self host × 2
- seq × 2
- sockets × 2
- stm × 2
- tcp × 2
- trie × 2
- tutorials × 2
- type × 2
- url × 2
- var × 2
- websharper.charting × 2
- websharper4 × 2
- websockets × 2
- wig × 2
- xna × 2
- zh × 2
- .net interop × 1
- 2012 × 1
- 4.0.194.126 × 1
- 4.1.3.184 × 1
- 4.1.4.189 × 1
- 4.2.0.214-beta × 1
- 4.2.12.259 × 1
- 4.2.2.231-beta × 1
- 4.2.8.255 × 1
- Canvas Sample Example × 1
- DynamicStyle Animated Style × 1
- Fixed in 4.0.190.100-rc × 1
- Released in Zafir.UI.Next.4.0.169.79-beta10 × 1
- SvgDynamicAttribute × 1
- WebComponent × 1
- abstract class × 1
- accumulator × 1
- active pattern × 1
- actor × 1
- addin × 1
- agents × 1
- aggregation × 1
- agile × 1
- alter session × 1
- animation × 1
- anonymous object × 1
- apache × 1
- api × 1
- appcelerator × 1
- architecture × 1
- array × 1
- arrays × 1
- asp.net 4.5 × 1
- asp.net core × 1
- asp.net integration × 1
- asp.net mvc 4 × 1
- asp.net web api × 1
- aspnet × 1
- ast × 1
- attributes × 1
- authorization × 1
- b-tree × 1
- back button × 1
- badimageformatexception × 1
- bash script × 1
- batching × 1
- binding-vars × 1
- bistro × 1
- body × 1
- bundle × 1
- camtasia studio × 1
- cas protocol × 1
- charts × 1
- clarity × 1
- class × 1
- cli × 1
- clipboard × 1
- clojurescript × 1
- closures × 1
- cloud × 1
- cms × 1
- coding diacritics × 1
- color highlighting × 1
- color zones × 1
- combinator × 1
- combinators × 1
- compile × 1
- compile code on server × 1
- config × 1
- confirm × 1
- content × 1
- context × 1
- context.usersession × 1
- continuation-passing style × 1
- coords × 1
- cordova × 1
- cors × 1
- coursera × 1
- cross-domain × 1
- csla × 1
- current_schema × 1
- custom content × 1
- data × 1
- data grid × 1
- datetime × 1
- debug × 1
- declarative × 1
- delete × 1
- devexpress × 1
- dhtmlx × 1
- dictionary × 1
- directattribute × 1
- disqus × 1
- distance × 1
- do binding × 1
- doc elt ui.next upgrade × 1
- docker × 1
- dojo × 1
- dol × 1
- dom × 1
- domain × 1
- du × 1
- duf-101 × 1
- dynamic × 1
- eastern language × 1
- eclipse × 1
- edsl × 1
- em algorithm × 1
- emacs × 1
- emotion × 1
- enums × 1
- error × 1
- etw × 1
- euclidean × 1
- eventhandlerlist × 1
- examples × 1
- ext js × 1
- extension methods × 1
- extra × 1
- facet pattern × 1
- failed to translate × 1
- fake × 1
- fantomas × 1
- fear × 1
- float × 1
- form × 1
- form-data × 1
- forum × 1
- fp × 1
- frank × 1
- fsdoc × 1
- fsharp × 1
- fsharp.core × 1
- fsharp.powerpack × 1
- fsharpx × 1
- fsunit × 1
- function × 1
- functional style × 1
- game × 1
- games × 1
- gc × 1
- generic × 1
- geometry × 1
- getlastwin32error × 1
- getting-started × 1
- google × 1
- google.maps × 1
- grid × 1
- group × 1
- guide × 1
- hash × 1
- headers × 1
- hello world example × 1
- heroku × 1
- highchart × 1
- history × 1
- how to × 1
- html-templating × 1
- http405 × 1
- httpcontext × 1
- hubfs × 1
- i18n × 1
- ie 8 × 1
- if-doc × 1
- iis × 1
- image × 1
- images × 1
- inheritance × 1
- initialize × 1
- input × 1
- install "visual studio" × 1
- installer × 1
- int64 × 1
- interfaces × 1
- internet explorer × 1
- interop × 1
- interpreter × 1
- io × 1
- iobservable × 1
- ios × 1
- iot × 1
- ipad × 1
- isomorphic × 1
- javascript optimization × 1
- javascript semanticui resources × 1
- jquery-plugin × 1
- jquery-ui × 1
- jquery-ui-datepicker × 1
- js × 1
- kendo datasource × 1
- kendochart × 1
- kendoui compiler × 1
- knockout × 1
- l10n × 1
- learning × 1
- library × 1
- libs × 1
- license × 1
- licensing × 1
- lineserieszonescfg × 1
- local setting × 1
- localization × 1
- logging × 1
- loop × 1
- macros × 1
- mailboxprocessor × 1
- mapping × 1
- maps × 1
- markerclusterer × 1
- markup × 1
- marshal × 1
- math × 1
- mathjax × 1
- message × 1
- message passing × 1
- message-passing × 1
- meta × 1
- metro style × 1
- micro orm × 1
- minimum-requirements × 1
- mix × 1
- mobile installation × 1
- mod_mono × 1
- modal × 1
- module × 1
- mouseevent × 1
- mouseposition × 1
- multidimensional × 1
- multiline × 1
- multithreading × 1
- mysql × 1
- mysqlclient × 1
- nancy × 1
- native × 1
- nested × 1
- nested loops × 1
- node × 1
- nunit × 1
- object relation mapper × 1
- object-oriented × 1
- om × 1
- onboarding × 1
- onclick × 1
- optimization × 1
- option × 1
- orm × 1
- os x × 1
- output-path × 1
- override × 1
- paper × 1
- parameter × 1
- persistence × 1
- persistent data structure × 1
- phonegap × 1
- pola × 1
- post × 1
- powerpack × 1
- prefix tree × 1
- principle of least authority × 1
- privacy × 1
- private × 1
- profile × 1
- programming × 1
- project × 1
- project euler × 1
- projekt_feladat × 1
- protected × 1
- provider × 1
- proxy × 1
- ptvs × 1
- public × 1
- pure f# × 1
- purescript × 1
- qna × 1
- quant × 1
- query sitelet × 1
- question × 1
- quotations × 1
- range × 1
- raphael × 1
- razor × 1
- rc × 1
- reactjs × 1
- real-time × 1
- ref × 1
- region × 1
- released in 4.0.190.100-rc × 1
- reporting × 1
- responsive design × 1
- rest api × 1
- rest sitelet × 1
- restful × 1
- round table × 1
- router × 1
- routing × 1
- rpc reverseproxy × 1
- runtime × 1
- sales × 1
- sample × 1
- sampleapp × 1
- scriptcs × 1
- scripting × 1
- search × 1
- self hosted × 1
- semanticui × 1
- sequence × 1
- serialisation × 1
- service × 1
- session-state × 1
- sharepoint × 1
- signals × 1
- sitelet website × 1
- sitelet.protect × 1
- sitlets × 1
- slickgrid × 1
- source code × 1
- sqlentityconnection × 1
- ssl × 1
- standards × 1
- static content × 1
- stickynotes × 1
- streamreader × 1
- stress × 1
- strong name × 1
- structures × 1
- submitbutton × 1
- subscribe × 1
- svg example html5 websharper.ui.next × 1
- sweetalert × 1
- system.datetime × 1
- system.reflection.targetinvocationexception × 1
- table storage × 1
- targets × 1
- tdd × 1
- templates ui.next × 1
- templating × 1
- text parsing × 1
- three.js × 1
- time travel × 1
- tls × 1
- tooltip × 1
- tracing × 1
- tsunamiide × 1
- turkish × 1
- twitter-bootstrap × 1
- type erasure × 1
- type inference × 1
- type providers × 1
- type-providers × 1
- typeprovider × 1
- ui next forms × 1
- ui-next × 1
- ui.next jqueryui × 1
- ui.next charting × 1
- ui.next formlets × 1
- ui.next forms × 1
- ui.next suave visualstudio × 1
- ui.next templating × 1
- unicode × 1
- unittest client × 1
- upload × 1
- usersession × 1
- validation × 1
- vb × 1
- vb.net × 1
- vector × 1
- view.map × 1
- visal studio × 1
- visual f# × 1
- visual studio 11 × 1
- visual studio 2012 × 1
- visual studio shell × 1
- vs2017 compiler zafir × 1
- vsix × 1
- web api × 1
- web-scraping × 1
- webapi × 1
- webcomponents × 1
- webforms × 1
- webgl × 1
- webrtc × 1
- webshaper × 1
- websharper async × 1
- websharper codemirror × 1
- websharper f# google × 1
- websharper forms × 1
- websharper reactive × 1
- websharper rpc × 1
- websharper sitelets routing × 1
- websharper warp × 1
- websharper-interface-generator × 1
- websharper.chartsjs × 1
- websharper.com × 1
- websharper.exe × 1
- websharper.owin × 1
- websharper.ui.next × 1
- websharper.ui.next jquery × 1
- websockets iis × 1
- why-websharper × 1
- windows 7 × 1
- windows 8 × 1
- windows-phone × 1
- winrt × 1
- www.grabbitmedia.com × 1
- xamarin × 1
- xml × 1
- yeoman × 1
- yield × 1
- zafir beta × 1
- zafir websharper4 × 1
- zarovizsga × 1
![]() |
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 |
Hello,
I am porting an ocaml game ([link:freetennis.sf.net)] to F# and .NET. I have been completely stuck for two days with what seems a socket problem, and I am in desperate need of help. :(
At each game frame (i.e. each iteration of the main loop), the client and the server must exchange, via sockets and TCP, an information packet (the state of the simulated world: the position of the players, etc). The number of frames per seconds (FPS) therefore depends on the round-trip speed (the time it takes for a packet to travel from client to server and back). The higher the FPS, the smoother the game is.
Porting the game to F#, and testing the game on my LAN, I discovered a huge latency which wasn't in the ocaml version, resulting in a very jerky motion and an unplayable game. A round trip of the packet was taking as much as 0.33 seconds, resulting in just 3 frames per second. On a LAN! (But only when client and server process are on two different machines. If they are on the same machine, the fps is much higher.)
I then created a test application in both ocaml and F#, which is attached below. The application simply serializes and transfers a pair of integers from client to server and back, at each frame. The client and server processes are on two different machines on a LAN. Here is the result:
With ocaml, the test application does 4700 frames per second.
With F#, the test application does about 3 frames per second.
The serialization is done with Marshal.to_channel in ocaml, BinaryFormatter.Serialize() in F#.
I also tried to rewrite everything so as not to use BinaryFormatter and Serialize/Deserialize, but only BinaryWriter and BinaryReader. This was a huge effort, involving serializing dozens of discriminated unions by hand, but in the end there was no gain in speed.
I then tried writing the test application in Visual Basic, and found it to have the same problem. So this may not be a F# problem, but a .NET problem with sockets. However I don't know what else to try. I am considering switching back to ocaml. Could somebody please tell me what to do or try? :)
Thanks a lot for any help.
Here is the test application in both ocaml and F#:
========================
F# version
========================
=========================
OCAML VERSION
=========================
Maurizio