Recently, I was asked to find the probability density function of distribution (pdf) of tossing 20 coins at a time. The time it took me to write a Monte Carlo simulation was half the time it took me to compute it from the binomial dist or by building a Pascal's triangle. Maybe thats not a good reflection on my math, but as you will see, I'm not a fancy Perl programmer either :)

Anyway, here is the code which produces very accurate results in a matter of 30sec on my modest laptop.

I hope you don't mind me modifying your script to make it a little more Perlish. It is more idiomatic (and often simpler) to use Perl-style rather than C-style loops. It is also, I think, preferable to declare variables inside the scope where they will be used rather than at the beginning of the script which, in effect, makes them global. I have renamed $toss to $tailsCt as I think that better describes its purpose. I have employed constants for number of runs and tosses just to illustrate their use. I also have added plenty of comments to clarify what I have done.

# Employ strictures and warnings.
#
use strict;
use warnings;
# Declare things we don't change as constants (by convention
# use upper-case for these).
#
use constant {
NUM_TOSSES => 20,
RUNS => 10_000_000,
};
# Initialise all possible elements of @collect to avoid
# "uninitialised" warnings at the printing stage if, for
# example, no "twenties" were thrown.
#
my @collect = map 0, 0 .. NUM_TOSSES;
# Use Perl-style for or foreach loops rather than C-style
# ones. The for and foreach are synonymous in Perl and can
# be freely interchanged. The loop variable is, by default,
# available in $_ or you can assign it to a lexically
# scoped variable as I do further down when printing the
# results. We don't need it in these nested loops.
#
for ( 1 .. RUNS )
{
# Declare lexically scoped $tailsCt so a new variable is
# brought into existence each time round the outer loop
# and will go out of scope on loop exit, thus no need to
# zeroise every time.
#
my $tailsCt;
# Toss the coin NUM_TOSSES times.
#
for ( 1 .. NUM_TOSSES )
{
# Give rand() an argument n and it will return a value
# such that 0 <= value < n. Use int to truncate so
# that $tailsCt is incremented by 0 or 1 (heads or
# tails respectively).
#
$tailsCt += int rand 2;
}
$collect[ $tailsCt ] ++;
}
# Rather than trying to line things up with tabs it might be
# easier to use printf and employ the same field widths in the
# heading and data rows. The RUNS constant is, in effect, a
# piece of code rather than a variable so we have to enclose
# it in a ${ \ ... } or a @{ [ ... ] } construct to interpolate
# it into a double-quoted string.
#
printf qq{%5s%25s%9s\n},
q{Tails}, qq{Count out of ${ \ RUNS }}, q{%ge};
# q{Tails}, qq{Count out of @{ [ RUNS ] }}, q{%ge}; also works
# Loop over the possible results from all heads to all tails
# using lexically scoped $tailsCt to access elements in the
# @collect array. This $tailsCt is a different variable to the
# $tailsCt in the for ( 1 .. RUNS ) { ... } loop above and is
# only in existence within the scope of this loop.
#
foreach my $tailsCt ( 0 .. NUM_TOSSES )
{
# Rather than using a temporary variable and sprintf you
# can just use printf directly and do the calculation in
# the argument list.
#
printf qq{%5d%25d%9.2f\n},
$tailsCt,
$collect[ $tailsCt ],
$collect[ $tailsCt ] / RUNS * 100;
}

Obviously your "modest laptop" is a bit zippier than my Atom-based desktop, as it took 3:10 to complete. But when I looked at your code, I was wondering why you did a monte-carlo simulation. Since there are only 20 coins, there are a little over a million combinations, so why not compute it exactly?

It runs quite a bit faster on my machine, and it tried each combination once. Of course, I'm limited by the number of bits available, so large experiments aren't going to work well. You can use Bit::Vector, but I suspect that would be a good deal slower. (I wouldn't know, I didn't try it.)

...find the probability density function of distribution (pdf) of tossing 20 coins at a time.

Let me add a pedantic point. When asked to find a PDF (e.g., in a statistical theory class), the requester is likely to mean that the function should be found analytically. That said, it is equally valid to compute it exactly by enumeration (as roboticus did).

Someone looking for a Monte Carlo approach will typically ask you to estimate the PDF.