Is it safe to create global variables using unsafePerformIO?

Hello! Lets consider the following code: import Control.Concurrent import Control.Concurrent.STM import System.IO.Unsafe (unsafePerformIO) {-# NOINLINE counter #-} counter :: TVar Int counter = unsafePerformIO $ newTVarIO 0 incCounter :: IO Int incCounter = do r <- atomically $ do t <- readTVar counter let t' = t + 1 writeTVar counter t' return t' return r main :: IO () main = do n1 <- incCounter print n1 n2 <- incCounter print n2 n3 <- incCounter print n3 This program prints: 1 2 3 So we have a "global variable". Do I right understand that newTVarIO creates TVar and RTS memoizes it since 'counter' function is pure? If it's true, could it happen that under some circumstances memoized value will be deleted from memory? Or Haskell keeps all memoized values forever? Another issue which I'm afraid of --- would the given code be safe in multithread application? For example, is it possible to encounter a race condition if two threads will try to create a new counter in the same time? Is there any other problems which should be taken in account? -- Best regards, Alexander Alexeev http://eax.me/

I believe you are safe to do this for now with GHC. There is some relevant
discussion here [1].
[1]: http://www.haskell.org/haskellwiki/Top_level_mutable_state
Note that doing:
{-# NOINLINE counter #-}
counter :: TVar Int
counter = unsafePerformIO $ atomically $ newTVar 0
Is not safe [2].
[2]:
http://hackage.haskell.org/package/stm-2.4.2/docs/Control-Concurrent-STM-TVa...
Ryan
On Wed, Jan 29, 2014 at 2:32 AM, Alexander Alexeev
Hello!
Lets consider the following code:
import Control.Concurrent import Control.Concurrent.STM import System.IO.Unsafe (unsafePerformIO)
{-# NOINLINE counter #-} counter :: TVar Int counter = unsafePerformIO $ newTVarIO 0
incCounter :: IO Int incCounter = do r <- atomically $ do t <- readTVar counter let t' = t + 1 writeTVar counter t' return t' return r
main :: IO () main = do n1 <- incCounter print n1 n2 <- incCounter print n2 n3 <- incCounter print n3
This program prints:
1 2 3
So we have a "global variable". Do I right understand that newTVarIO creates TVar and RTS memoizes it since 'counter' function is pure? If it's true, could it happen that under some circumstances memoized value will be deleted from memory? Or Haskell keeps all memoized values forever?
Another issue which I'm afraid of --- would the given code be safe in multithread application? For example, is it possible to encounter a race condition if two threads will try to create a new counter in the same time?
Is there any other problems which should be taken in account?
-- Best regards, Alexander Alexeev http://eax.me/ _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

I was also wondering a similar thing. I'm writing FFI for a C library. Library has a function like: int pollEvent(EventType* event); Instead of malloc'ing a new EventType in a FFI call for this functions: pollEvent :: IO Event pollEvent = do ev <- malloc ret <- cPollEvent ev -- check if ret is 0 etc. peek ev I was wondering if something like this is also safe: eventObj_ :: Ptr Event eventObj_ = unsafePerformIO malloc pollEvent :: IO Event pollEvent = do ret <- cPollEvent eventObj_ -- check if ret is 0 etc. peek eventObj_ This is one malloc cheaper for every call, and differences are not visible from user side. Still, I did not use this in my FFI bindings because I was not sure how safe is this approach. Any ideas on this? --- Ömer Sinan Ağacan http://osa1.net

You would need the NOINLINE pragma:
{-# NOINLINE eventObj_ #-}
eventObj_ :: Ptr Event
eventObj_ = unsafePerformIO malloc
I would avoid this sort of global state in general. It isn't clear that
this will give an improvement in performance and what could otherwise
possible be a thread safe API is no longer thread safe. Global TVars and
MVars are much more compelling as they are thread safe and represent some
global synchronization in your program.
On Wed, Jan 29, 2014 at 10:03 AM, Ömer Sinan Ağacan
I was also wondering a similar thing. I'm writing FFI for a C library. Library has a function like:
int pollEvent(EventType* event);
Instead of malloc'ing a new EventType in a FFI call for this functions:
pollEvent :: IO Event pollEvent = do ev <- malloc ret <- cPollEvent ev -- check if ret is 0 etc. peek ev
I was wondering if something like this is also safe:
eventObj_ :: Ptr Event eventObj_ = unsafePerformIO malloc
pollEvent :: IO Event pollEvent = do ret <- cPollEvent eventObj_ -- check if ret is 0 etc. peek eventObj_
This is one malloc cheaper for every call, and differences are not visible from user side. Still, I did not use this in my FFI bindings because I was not sure how safe is this approach. Any ideas on this?
--- Ömer Sinan Ağacan http://osa1.net _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

You would need the NOINLINE pragma
Ahh, right. Thanks for reminding.
It isn't clear that this will give an improvement in performance
Why is that? I think it's clear since I'm eliminating malloc calls. (though one may claim that malloc calls are so cheap it's not even measurable)
and what could otherwise possible be a thread safe API is no longer thread safe.
This is a fair point. Though in my case I don't think this is the case because this API is already not thread safe, because of the C API and not Haskell bindings. --- Ömer Sinan Ağacan http://osa1.net

On Wed, Jan 29, 2014 at 1:09 PM, Ömer Sinan Ağacan
It isn't clear that this will give an improvement in performance
Why is that? I think it's clear since I'm eliminating malloc calls. (though one may claim that malloc calls are so cheap it's not even measurable)
(1) it's not really malloc since it's being garbage collected when out of scope (2) given that there is a lot of allocation anyway and anything allocated as ephemerally as this use case is very common and very highly optimized, it's not really worth it -- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net

You might be interested in
http://hackage.haskell.org/package/global-variables for this.
Cheers
Johan
On Jan 29, 2014 7:17 PM, "Brandon Allbery"
On Wed, Jan 29, 2014 at 1:09 PM, Ömer Sinan Ağacan
wrote: It isn't clear that this will give an improvement in performance
Why is that? I think it's clear since I'm eliminating malloc calls. (though one may claim that malloc calls are so cheap it's not even measurable)
(1) it's not really malloc since it's being garbage collected when out of scope
(2) given that there is a lot of allocation anyway and anything allocated as ephemerally as this use case is very common and very highly optimized, it's not really worth it
-- brandon s allbery kf8nh sine nomine associates allbery.b@gmail.com ballbery@sinenomine.net unix, openafs, kerberos, infrastructure, xmonad http://sinenomine.net
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
participants (5)
-
Alexander Alexeev
-
Brandon Allbery
-
Johan Holmquist
-
Ryan Yates
-
Ömer Sinan Ağacan