Using Gnu/Linux System Call 'Splice' for Zero-Copy Socket to Socket Data Transfers in Haskell

Using GNU/Linux system call `splice` for zero-copy Socket to Socket data transfers in Haskell

I don't know Haskell, but "resource temporarily unavailable" is EAGAIN.

And it looks like Haskell sets its sockets to non-blocking mode by default. So if you try to read from one when there is no data, or try to write to one when its buffer is full, you will fail with EAGAIN.

Figure out how to change the sockets to blocking mode, and I bet you will solve your problem.

[update]

Alternatively, call select or poll before attempting to read or write the socket. But you still need to handle EAGAIN, because there are rare corner cases where Linux select will indicate a socket is ready when actually it isn't.

Haskell: what is the fastest way to pipe data from handle to a handle?

I don't think you'll find any non-FFI solution that significantly beats either:

almostForever $ Data.ByteString.hGetSome h1 nr >>= Data.ByteString.hPutStr h2

Or perhaps you'd gain a little using lazy byte strings:

Data.ByteString.Lazy.hGetContents  h1 >>= Data.ByteString.Lazy.hPut h2

If you have time then run a benchmark of these. If you don't have time, then just do one and don't worry so much about performance unless it's actually an issue.

Does Linux's splice(2) work when splicing from a TCP socket?

What kernel version is this? Linux has had support for splicing from a TCP socket since 2.6.25 (commit 9c55e01c0), so if you're using an earlier version, you're out of luck.

Do other operating systems implement the Linux system call splice?

OpenBSD has sosplice and somove: http://www.openbsd.org/cgi-bin/man.cgi?query=sosplice

Is 300ms for encrypting and then decrypting a single 8 * 1K message an acceptable RSA performance?

First off, good question - the performance difference of RSA to OpenSSL is a question I had too. That said, here's a bunch of text that doesn't give the answer.

The Haskell RSA Package Changed

I've recently moved RSA to using CryptoRandomGen from RandomGen. You are using the painfully slow StdGen so switching to the generator in the intel-aes package or HashDRBG (perhaps a buffered version) from the DRBG package will help.

This is not how you're supposed to use Public Key Cryptography

Generally you use public keys to either exchange a secret key or encrypt a secret key such that only the recipient can decrypt it. You seem to be intending to use RSA to continually encrypt a stream of messages. The performance of RSA is of such little concern to people precisely because it is such a rare operation.

Proper Benchmarking

As Daniel said, you are currently benchmarking key generation, encryption and decryption all in one batch. You responded that you won't be generating many keys, just doing lots of enc/dec operations... so don't you think you should fix the benchmark?

Also, you're benchmark seems incomplete and thus suspect - at the very least it's missing an import.

Other Alarming Things

You say "Randomnes of key pairs [are] of no importance at the moment." Until they are important, there is no reason bothering with cryptography.

Benchmarking
Oli also had a good point. Benchmarking OpenSSL is the way to go.

From the command line (which as far as I'm going with the part of the answer) OpenSSL forces you to use RSA semi-correctly, so we'll just be benchmarking encryption of really small files:

dd if=/dev/urandom of=64B bs=64 count=1
openssl genrsa -out test.key 1024
openssl rsa -in test.key -out public.pem -outform PEM -pubout
openssl rsa -in test.key -out private.pem -outform PEM
time openssl rsautl -raw -ssl -encrypt -inkey private.pem -in 64B -out 64B.enc

Which gives us anywhere from 5 to 12 ms.

Now for the Haskell. Aside from cosmetic changes, I've moved to the new RSA using CryptoRandomGen and the not-so-fast but OK HashDRBG generator at the same time as making your encrypt function pure and ditching the unneeded comparison. We end up with:

import Criterion.Main
import Criterion.Config
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Codec.Crypto.RSA
import Crypto.Random.DRBG

main :: IO ()
main = do
r1 <- newGenIO :: IO HashDRBG
r2 <- newGenIO :: IO (GenBuffered HashDRBG)

-- We don't care about the performance of generate, so we do it outside the benchmark framework
let (pub,priv,g2) = generateKeyPair r2 1024

defaultMainWith defaultConfig (return ()) [
bgroup "RSA" [
bench "1" $ whnf (enc r1 pub priv) m1
, bench "2" $ whnf (enc r2 pub priv) m1
]
]

m1 :: L.ByteString
m1 = L.pack [0..63]

enc :: CryptoRandomGen g => g -> PublicKey -> PrivateKey -> L.ByteString -> L.ByteString
enc g pub priv m =
let (em,ng) = encrypt g pub m
dm = decrypt priv em
in dm

This yields measurements around 3.5ms (compiled with GHC 7.4 and -O2). To be clear: I'm not saying RSA is faster than OpenSSL - the OpenSSL test had a LOT more overhead (loading the executable, reading the key, reading the plaintext, encrypting, writing the result) and it very believably could be an order of magnitude faster than the RSA package. What I am saying is "Hey look, the Haskell RSA code performed arbitrarily fast to the point where I don't really care and you can perfect the benchmark more if you'd like."

For reference, openssl speed rsa1024 says it sign's in 0.5 ms (on my machine, obviously), which I suspect is an RSA encrypt of 16 bytes along with other operations.

Using Haskell to interpret multi-line strings of code at runtime

I haven't used hint, but I can tell you that your example is not a valid Haskell expression. where clauses are not attached to expressions, they are attached to definitions. That is, you have to have an = sign to be able to have a where clause.

-- Correct
foo = bar
where
bar = baz
where
baz = 42

-- Incorrect
foo = (bar + 1 where bar = 41)

If you want to define something in expression context you must use let

let concatString :: String -> String -> String
concatString str1 str2 = str1 ++ str2
in concatString "Hello" "World"


Related Topics



Leave a reply



Submit