├── .gitmodules ├── LICENSE ├── README.md └── images ├── comparison.gif └── comparison2.gif /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "csharp"] 2 | path = csharp 3 | url = https://github.com/jeremybytes/digit-display-csharp-comparison 4 | [submodule "golang"] 5 | path = golang 6 | url = https://github.com/jeremybytes/digit-display-golang-comparison 7 | [submodule "rust-lang"] 8 | path = rust-lang 9 | url = https://github.com/jeremybytes/digit-display-rust-lang-comparison 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jeremy Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Digit Display - Comparing 3 Language Implementations 2 | This code was written as an exercise to learn some differences between Rust (rust-lang), Go (golang), and C# (csharp). The same problem and general solution was implemented in all 3 languages/environments. 3 | 4 | **Program Purpose:** 5 | 6 | Naive hand-written digit recognition with display applications to show image, prediction, and errors. 7 | 8 | The original .NET (F# & C#) implementation of this project is available here: [https://github.com/jeremybytes/digit-display](https://github.com/jeremybytes/digit-display). The C# version varies from the original implementation for consistency purposes. 9 | 10 | These implementations were primarily for language practice and do not pretend to be fully optimized. 11 | 12 | **Functions** 13 | * Reading files from the file system 14 | * Training simple nearest-neighbor digit recognizers 15 | * Manhattan distance 16 | * Euclidean distance 17 | * Output (pretty bad) ASCII art 18 | * Multi-threading 19 | * Channels 20 | * Chunking / threading 21 | * Parsing command-line parameters 22 | 23 | **Usage** 24 | 25 | *csharp* 26 | ``` 27 | PS C:\...> .\digits.exe --help 28 | digits 1.0.0 29 | Copyright (C) 2022 digits 30 | 31 | -o, --offset (Default: 1000) Offset in the data set (default: 1000) 32 | -c, --count (Default: 100) Number of records to process (default: 100) 33 | --classifier (Default: euclidean) Classifier to use (default: 'euclidean') 34 | -t, --threads (Default: 6) Number of threads to use (default: 6) 35 | --help Display this help screen. 36 | --version Display version information. 37 | ``` 38 | 39 | *golang* 40 | ``` 41 | PS C:\...> .\digit-display-golang.exe --help 42 | Usage of C:\...\digit-display-golang.exe: 43 | -class string 44 | classifier calculation type - currently supported: 'manhattan', 'euclidean' (default "euclidean") 45 | -count int 46 | number of records to identify (default 100) 47 | -offset int 48 | starting record in data set (default 1000) 49 | -threads int 50 | number of threads to use (default 6) 51 | ``` 52 | 53 | *rust-lang* 54 | ``` 55 | PS C:\...> .\digits.exe --help 56 | digits 1.0 57 | Jeremy Clark 58 | parses hand-written digits 59 | 60 | USAGE: 61 | digits.exe [OPTIONS] 62 | 63 | FLAGS: 64 | -h, --help Prints help information 65 | -V, --version Prints version information 66 | 67 | OPTIONS: 68 | --classifier Classifier to use (default: 'euclidean') 69 | -c, --count Number of records to process (default: 100) 70 | -o, --offset Offset in the data set (default: 1000) 71 | -t, --threads Number of threads to use (default: 6) 72 | ``` 73 | 74 | **Release Builds** 75 | The speed comparison shown below is based on release builds of the projects. Release builds used the following command-line options: 76 | 77 | *csharp* 78 | ``` 79 | dotnet build -c release 80 | ``` 81 | 82 | *golang* 83 | ``` 84 | go build -ldflags "-s -w" 85 | ``` 86 | 87 | *rust-lang* 88 | ``` 89 | cargo build --release 90 | ``` 91 | 92 | **Speed Comparison** 93 | 94 | > Disclaimer: 95 | > The code in each environment is in no way optimized. The main experiment was to use similar constructs where possible (eg. using Channels to communicated between parallel/async operations and to control the number of threads used by the processes). I would expect that manual threading or other language features could be used to speed up operations. 96 | 97 | 98 | The following shows a speed comparison between the various environments. These use the following parameters: 99 | * Classifier: euclidean 100 | * Offset: 1000 101 | * Count: 2000 102 | * Threads: 13 103 | 104 | *Note about threads: I'm using a machine with 16 virtual cores. I generally run these applications at 15 threads to leave room for the console output to process. In this case, I went back to 13 to give a bit more space for the screen-capture software.* 105 | 106 | All outputs show the same number of errors (since they use the same algorithm and data set). 107 | 108 | *csharp* 109 | 110 | *Updated 2/23/2022: original time was 00:00:20.7 - see "Performance Updates" notes below.* 111 | ``` 112 | Using Euclidean Classifier -- Offset: 1000 Count: 2000 113 | Total time: 00:00:05.0355685 114 | Total errors: 59 115 | ``` 116 | 117 | *golang* 118 | ``` 119 | Using Euclidean Classifier -- Offset: 1000 Count: 2000 120 | Total time: 14.2979381s 121 | Total errors: 59 122 | ``` 123 | 124 | *rust-lang* 125 | ``` 126 | Using Euclidean Classifier -- Offset: 1000 Count: 2000 127 | Total time (seconds): 6.999 128 | Total errors: 59 129 | ``` 130 | 131 | **Screen Capture** 132 | The following runs were captured separately (so each would have the full use of the machine (or as much of the machine that was available apart from the OS and other bits)). The results were then combined into a single video. 133 | 134 | *Original Screen Capture* 135 | ![Screen capture comparison](/images/comparison.gif) 136 | 137 | *Updated Screen Capture* 138 | ![Screen capture comparison](/images/comparison2.gif) 139 | The current projects show C# as the fastest of the 3. 140 | Summary: 141 | * C# - 5-ish seconds (down from 20 seconds) 142 | * Go - 14-ish seconds 143 | * Rust - 7-ish seconds 144 | 145 | The numbers vary a bit from those above, but remain pretty consistent on my machine. 146 | 147 | **Performance Updates** 148 | Based on feedback from several folks in the community, I made updates to the C# version of the application. (Suggestions for improving performance on the Go and Rust projects would be appreciated). 149 | 150 | Additional information on the C# changes will follow. Here is the short version. 151 | * List<int> changed to int[] 152 | * String concatenation changed from string+= to using StringBuilder 153 | * Abstract override method was moved out of a tight loop to a higher level. This results in duplicated code but a very large change in performance. 154 | 155 | **Thoughts** 156 | As an experiment, this was quite interesting to me. Go and C# are similar in that they are garbage-collected languages (which adds some overhead), so it does not suprirse me that Rust was faster in the original implementations. 157 | 158 | Because of the memory model in Rust, that version was significantly more challenging to write. The C# code could not be ported directly; there are quite a few inefficiencies that I added to get the Rust code to work. 159 | 160 | If you have ideas on "fixing" any of these implementations, I'm interested in seeing them; however, this set of code will most likely remain fairly static. -------------------------------------------------------------------------------- /images/comparison.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremybytes/digit-display-language-comparison/96a93ca5f5202e5020130d0bbcf7a0c59fc256e7/images/comparison.gif -------------------------------------------------------------------------------- /images/comparison2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremybytes/digit-display-language-comparison/96a93ca5f5202e5020130d0bbcf7a0c59fc256e7/images/comparison2.gif --------------------------------------------------------------------------------