Shop OBEX P1 Docs P2 Docs Learn Events
Counting Free Cogs — Parallax Forums

Counting Free Cogs

RossHRossH Posts: 5,519
edited 2011-03-17 21:21 in Propeller 1
All,

I recently had a need for a function to return the current number of free cogs. Here is something I whipped up in SPIN that did what I needed:
CON

  STACKSIZE  = 4 ' what is the smallest size this could be?

VAR
  LONG Status[ 8 ]
  LONG Stack[ 8*STACKSIZE ]

'
' Set Status - set our value in the Status array to 1
'
PRI Set_Status

   Status[ cogid ] := 1

   ' now just wait forever
   repeat 

'
' Start Set_Status in free cogs until we can't run any more, then count them
'
PUB Free_Cog_Count : count | i

  repeat i from 0 to 7
     Status[ i ] := 0

  repeat i from 0 to 7
     if cognew(Set_Status, @Stack[ STACKSIZE*i ]) < 0
        ' no more cogs, so ...
        quit

  repeat i from 0 to 7
     if Status[ i ] > 0
       cogstop(i)
       count++

  return count

I didn't need it to be very efficient, but it strikes me that this would be a useful function to keep in my box of 'Propeller tricks' - but those who are better at SPIN than I am should be able to do it in much less space (this version takes 96 code bytes, plus 40 longs!).

Anybody?
«1

Comments

  • Bill HenningBill Henning Posts: 6,445
    edited 2011-03-16 21:03
    Warning: NOT tested!
    var
    	long stack[10], freecogs
    
    PUB dummy
    	freecogs++
    	waitcnt(cnt+40_000_000)
    	cogstop(cogid)
    
    PUB cogsfree
    	freecogs:=0
    	repeat while cognew(dummy,@stack) => 0
    	return freecogs
    
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-16 21:08
    Bill, I think you want repeat while. Also, I'm not entirely sure that stack-sharing won't lead to some difficulties here.

    -Phil
  • RossHRossH Posts: 5,519
    edited 2011-03-16 21:12
    Hi Bill,

    Neat idea - but all your instances of "dummy" are using the same stack. Won't this cause problems?

    Also (and I know I didn't say this, but I should have) the counting function should not return till all the cogs are free again. The main reason you would want to check how many cogs are free is that you want to use one, but with this solution you would get told there are "n" cogs free, but when you actually try and use one there won't be any!

    Ross.
  • Bill HenningBill Henning Posts: 6,445
    edited 2011-03-16 21:30
    Thanks Phil
  • Bill HenningBill Henning Posts: 6,445
    edited 2011-03-16 21:39
    Same stack should be fine, but it would be easy to use multiple stacks (like in your idea)

    Good point about returning.
    var
    	long freecogs,stack[32]
    
    PUB dummy
    	freecogs++
    	waitcnt(cnt+40_000)
    	cogstop(cogid)
    
    PUB cogsfree
    	freecogs:=0
    	repeat while cognew(dummy,@stack[freecogs<<2]) => 0
    	waitcnt(cnt+80_000)
    	return freecogs
    
    RossH wrote: »
    Hi Bill,

    Neat idea - but all your instances of "dummy" are using the same stack. Won't this cause problems?

    Also (and I know I didn't say this, but I should have) the counting function should not return till all the cogs are free again. The main reason you would want to check how many cogs are free is that you want to use one, but with this solution you would get told there are "n" cogs free, but when you actually try and use one there won't be any!

    Ross.
  • AribaAriba Posts: 2,690
    edited 2011-03-16 23:43
    This needs 12 longs and no var memory: (and is tested)
    pub FreeCogs : num 
     num := 8
     repeat num
       if cognew(@asmcog,0) < 0
         num--
      
    DAT
            org 0
    asmcog  add t1,cnt
            waitcnt t1,#0
            cogid t1
            cogstop t1
    t1      long 100_000
    

    Andy
  • RossHRossH Posts: 5,519
    edited 2011-03-17 01:19
    Hi Ariba,

    Thanks - this is more like the size I had in mind. But it suffers the same problem as Bill's solution - i.e. that you have to wait a certain amount of time after you have calculated the number of free cogs before any use can be made of this knowledge - because the cogs that are supposed to be free will not actually be available for use (in this case, for the next 100_000 cycles).

    Perhaps we could instead take a slightly different approach - if the code returned the identity of the free cogs (in a bitset) rather than just the number of them, then programs could then use a coginit instead of a cognew.

    Ross.
  • AribaAriba Posts: 2,690
    edited 2011-03-17 02:44
    Okay, here another approach:
    PUB FreeCogs : num | i,status,stat2
      repeat i from 0 to 7
        if (byte[@status][i] := cognew(@asmcog,0)) => 0
          num++
      repeat i from 0 to 7
        if byte[@status][i] < 255
          cogstop(byte[@status][i])
             
    DAT
    asmcog  jmp #asmcog
    

    but needs 2 longs more :smile:

    Andy
  • RossHRossH Posts: 5,519
    edited 2011-03-17 03:11
    Ariba wrote: »
    Okay, here another approach:
    PUB FreeCogs : num | i,status,stat2
      repeat i from 0 to 7
        if (byte[@status][i] := cognew(@asmcog,0)) => 0
          num++
      repeat i from 0 to 7
        if byte[@status][i] < 255
          cogstop(byte[@status][i])
             
    DAT
    asmcog  jmp #asmcog
    
    but needs 2 longs more :smile:

    Andy

    Nice!
  • Heater.Heater. Posts: 21,230
    edited 2011-03-17 03:46
    And now can we have a recursive solution:)
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-17 08:15
    Here's a simpler one:
    PUB CountFreeCogs
      repeat 7
        cognew(@gobble, 0)
      return 0
    
    DAT
    
    gobble  jmp  #gobble
    
    :)

    -Phil
  • Heater.Heater. Posts: 21,230
    edited 2011-03-17 08:30
    Phil,
    You are not trying hard enough. You could make that faster by terminating the loop when cognew fails:-)
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2011-03-17 08:31
    I agree with Phil; why would you not keep tract of the number of cogs used from the beginning? This is like all the programmers that rely on the compiler to preform array bounds checking, instead of doing it right and keeping tract of the upper and lower bounds relative to the the index. I guess some people prefer bloated and slow code :) .
  • Heater.Heater. Posts: 21,230
    edited 2011-03-17 08:37
    I think Phils "gobble" solution is the only one I have seen so far that is always guranteed to give the correct result.

    Does anyone know why I might think so?
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2011-03-17 09:00
    Well let us examine the case were in another running cog starts another cog after the cogs are counted though before the cog count function returns. This makes it fairly clear that the only way to know the number of free cogs is to keep count any time that any cog starts another cog. and even this is not for sure, as another cog could start a new cog after the count is read though before your code uses the count.
  • lonesocklonesock Posts: 917
    edited 2011-03-17 09:08
    EDIT: now tested...had to change a if_z to if_nz
    [8^)
    PUB FreeCogs | stayin_alive
      stayin_alive~~
      repeat 7
        result -= (cognew( @loiter_cog, @stayin_alive ) > -1)
      stayin_alive~
    
    DAT
    loiter_cog              rdlong  tmp, par wz
                  if_nz      jmp     #loiter_cog
                            cogid   tmp
                            cogstop tmp
    tmp     res   1
    
    FreeCogs is 20 bytes, plus the 4 longs for the PASM code.

    Jonathan
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2011-03-17 09:09
    If you want an accurate count of used and free cogs, make sure that only one cog runs code that may start a new cog, and that all other cogs communicate with this cog in order to start a new cog as well as to stop a cog, and have this 'Master Cog" keep a counter incrementing for each new cog and decrementing each time a cog is stopped.

    I can not see any other way to get an accurate count given all situations. If more than one cog executes code to start and or stop a cog you can not have an accurate count.
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-17 09:09
    If you don't include a cogstop at the end the the obvious answer will be zero. That is, there will be no free cogs available. The routine will need to keep track of which cogs get started with the dummy code, and then do a cogstop on them before returning.

    EDIT: Oh, I now see that Phil's "solution" was intended as a joke.
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2011-03-17 09:14
    Ok I guess you could have a lock, and enforce timing on the execution of COGINIT as well as of COGSTOP opcodes.
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-17 09:30
    12 longs, or 10 longs if you don't count the method table.
    PUB FreeCogs : num | i,status,stat2
      repeat 7
        if (i := cognew(@asmcog, 0)) < 0
          quit
        byte[@status][num++] := i
      i := @status
      repeat num
        cogstop(byte[i++])
             
    DAT
    asmcog  jmp #asmcog
    
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-17 09:49
    11 longs, or 9 longs without the method table.
    PUB FreeCogs : num | i,status,stat2
      i := @status
      repeat while (byte[i][num++] := cognew(@asmcog, 0)) => 0
      repeat --num
        cogstop(byte[i++])
             
    DAT
    asmcog  jmp #asmcog
    
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-17 09:49
    Dave,

    That's some very elegant Spin!

    -Phil
  • Dave HeinDave Hein Posts: 6,347
    edited 2011-03-17 10:03
    Thanks, Phil. I've looked at Spin bytecodes enough to know which operations take the least number of bytes.
  • RossHRossH Posts: 5,519
    edited 2011-03-17 14:42
    All,

    Well, I can't fault Phil's method for accuracy :smile: - but I think Dave's method wins on size and utility!

    Ross
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2011-03-17 18:54
    As I had said the only reliable way is to require that all cogs report somehow when ever starting a new cog, for example:
    CON
        _CLKMODE      = XTAL1 + PLL16X
        _XINFREQ      = 5_000_000
    PUB START(RRR)
      COGINIT(7,@LOADAPP,@MAINAPP)
    DAT
            ORG 0
    LOADAPP
            MOV GOCOG, PAR
            SHL GOCOG, #4
            COGINIT GOCOG
            MOV BCOG, #7
            COGSTOP BCOG
    
    BCOG    LONG 0
    GOCOG   LONG 0
    
    MAINAPP ORG 0
            WRLONG COGCNT,#1
            LOCKNEW COGLCK
    
    COGINC  'Code to include in all cogs.
            RDLONG COGLCKH, COGLCK
    
            '... your COG 0 Code.
    
    COGBGN  'Include in all cogs that start a cog
            '  and use instead of cognew.
            LOCKSET COGLCK  WC
      IF_C  JMP COGBGN
            RDLONG COGCNT, LCOGCNT
            CMP LCOGCNT, 8  WC, WZ
      IF_B  MOV MGOCOG, NXTPAR
      IF_B  SHL MGOCOG, #14
      IF_B  MOV MGOCOG, NXTASM 'Use your own ptrs.
      IF_B  SHL MGOCOG, #4
      IF_B  OR MGOCOG, #8
      IF_B  COGINIT MGOCOG
      IF_B  ADD LCOGCNT, #1
      IF_B  WRLONG COGCNT, LCOGCNT
      IF_AE MOV EE, #$FF
            LOCKCLR COGLCK
            RET
    
    COGSTP  'Include in all cogs that stop any cog
            '  and use instead of cogstop.
            LOCKSET COGLCK  WC
      IF_C  JMP COGBGN
            RDLONG COGCNT, LCOGCNT
            COGSTOP STCOG
            SUB LCOGCNT, #1
            LOCKCLR COGLCK
            RET
    
    EE      LONG 0  'Reg to track errors
    STCOG   LONG 0  'Parameter for cogstp.
    NXTPAR  LONG 0  'REPLACE WITH ACTUAL PAR TO PASS.
    NXTASM  LONG $0000_7000 'Replace for your needs.
    COGCNT  LONG $0000_7F00
    LCOGCNT LONG 1
    COGLCKH LONG $0000_7F04
    COGLCK  LONG 0
    WCOG    LONG 0
    MGOCOG  LONG 0
    

    NOTE: THIS CODE IS UNTESTED. THIS IS THE FIRST TIME I HAVE USED THE LOCK OPS.

    Please point out any optimizations and/or errors that I may have made.
  • RossHRossH Posts: 5,519
    edited 2011-03-17 19:29
    As I had said the only reliable way is to require that all cogs report somehow when ever starting a new cog

    This is fine if you are writing code completely from scratch - but most people make use of other code that already exists - and that code won't be quite so co-operative!

    It would have been good if the Prop had included a built in register that showed the current run state of all cogs. But even that would be fooled on occasion (e.g. if a new cog is started between the time you checked the register and the time you tried to use what you thought was a free cog!).

    I wonder if it's too late for the Prop II?:smile:
  • Phil Pilgrim (PhiPi)Phil Pilgrim (PhiPi) Posts: 23,514
    edited 2011-03-17 19:47
    RossH wrote:
    But even that would be fooled on occasion (e.g. if a new cog is started between the time you checked the register and the time you tried to use what you thought was a free cog!).
    And why would you care if a particular cog were free? cognew eliminates that worry completely. (Oh, please tell me you weren't thinking of using coginit! :) )

    -Phil
  • davidsaundersdavidsaunders Posts: 1,559
    edited 2011-03-17 19:50
    @RosH
    This is fine if you are writing code completely from scratch - but most people make use of other code that already exists - and that code won't be quite so co-operative!

    Ah I could see this as a problem. I have always preferred to write my own code from the ground up only including existing routines as I must, and then I only use routines written by others if I truly do not understand the core concepts. This is why I have never liked the standard libraries of most programming languages.
  • RossHRossH Posts: 5,519
    edited 2011-03-17 19:56
    And why would you care if a particular cog were free? cognew eliminates that worry completely. (Oh, please tell me you weren't thinking of using coginit! :) )

    -Phil

    Hi Phil,

    I hate to tell you this, but I use coginit quite a lot - e.g. in the Catalyst loader.

    For instance, some programs fail to run if they are run by a SPIN interpreter that is not running in cog 0.

    Other times programs have to restart the cog they are currently running in.

    Is there a reason not to use coginit?

    Ross.
  • kuronekokuroneko Posts: 3,623
    edited 2011-03-17 20:53
    RossH wrote: »
    For instance, some programs fail to run if they are run by a SPIN interpreter that is not running in cog 0.
    Then those programs need fixing. Period.
    RossH wrote: »
    Other times programs have to restart the cog they are currently running in.
    Different issue really. You already own the cog so no harm done.
Sign In or Register to comment.