2
\$\begingroup\$

I have made a non-threaded version of counting words. The result was unexpected the non-threaded version was faster than threaded-parallel version.

Non-threaded version:

//
// program name: WordCounter
//
// usage: C:\>WordCounter.exe test.txt test1.txt 
//
#include <future>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <unordered_map>
#include <string>
#include <vector>
#include <algorithm>
#include <exception>

#include "Profiler.h"

using Words = std::unordered_map < std::string, std::size_t >;

Words loadFromFile(const std::string& fileName)
{
    std::ifstream file(fileName);

    if (!file)
    {
        throw "Can't open the file " + fileName;
    }

    Words words;

    for (std::string word; file >> word;)
    {
        ++words[word];
    }

    return words;
}

template<typename T>
void showResult(T begin, T end)
{
    std::vector<T> words;

    words.reserve(std::distance(begin, end));

    for (auto i = begin; i != end; ++i)
    {
        words.push_back(i);
    }

    std::sort(words.begin(), words.end(),
        [](const T& it1, const T& it2)
    {
        return it1->second > it2->second;
    });

    int x = 0;

    for (const auto& i : words)
    {
        if (x++ == 0)
        {
            std::cout << "\nThe most repeated element is: " << i->first << "\n\n";

            std::cout << " Word\tRepeated Times";

            std::cout << '\n' << std::setfill('-') << std::setw(28) << '\n';
        }

        std::printf(" %-10s%5d\n", i->first.c_str(), i->second);
    }

}

int main(int argc, char *argv[])
{
    profiler.begin("test");

    std::vector<Words> resources;

    resources.reserve(argc - 1);

    for (int i = 1; i < argc; ++i)
    {
        resources.push_back(loadFromFile(argv[i]));
    }

    Words words;

    for (auto& i : resources)
    {
        Words result = i;

        for (auto& j : result)
        {
            words[j.first] += j.second;
        }
    }

    showResult(words.begin(), words.end());

    profiler.end("test");

    std::cout << '\n' << profiler.data("test") << " seconds\n";
}

Threaded-parallel version:

//
// program name: WordCounter
//
// usage: C:\>WordCounter.exe test.txt test1.txt 
//
#include <future>
#include <iostream>
#include <iomanip>
#include <fstream>
#include <unordered_map>
#include <string>
#include <vector>
#include <algorithm>
#include <exception>

#include "Profiler.h"

using Words = std::unordered_map < std::string, std::size_t >;

Words loadFromFile(const std::string& fileName)
{
    std::ifstream file(fileName);

    if (!file)
    {
        throw "Can't open the file " + fileName;
    }

    Words words;

    for (std::string word; file >> word;)
    {
        ++words[word];
    }

    return words;
}

template<typename T>
void showResult(T begin, T end)
{
    std::vector<T> words;

    words.reserve(std::distance(begin, end));

    for (auto i = begin; i != end; ++i)
    {
        words.push_back(i);
    }

    std::sort(words.begin(), words.end(),
        [](const T& it1, const T& it2)
    {
        return it1->second > it2->second;
    });

    int x = 0;

    for (const auto& i : words)
    {
        if (x++ == 0)
        {
            std::cout << "\nThe first most repeated element is: " << i->first << "\n\n";

            std::cout << " Word\tRepeated Times";

            std::cout << '\n' << std::setfill('-') << std::setw(28) << '\n';
        }

        std::printf(" %-10s%5d\n", i->first.c_str(), i->second);
    }
}

int main(int argc, char *argv[])
{
    profiler.begin("test");

    std::vector<std::future<Words>> resources;

    resources.reserve(argc - 1);

    for (int i = 1; i < argc; ++i)
    {
        resources.push_back(std::async(loadFromFile, argv[i]));
    }

    Words words;

    for (auto& i : resources)
    {
        Words results;

        try
        {
            results = i.get();
        }
        catch (std::exception &err)
        {
            std::cerr << "ERROR: " << err.what() << '\n';
        }
        catch (std::string &err)
        {
            std::cerr << "ERROR: " << err << '\n';
        }

        for (const auto& j : results)
        {
            words[j.first] += j.second;
        }
    }

    showResult(words.begin(), words.end());

    profiler.end("test");

    std::cout << '\n' << profiler.data("test") << " seconds\n";
}

Profiler header file:

#pragma once
#ifndef PROFILER_H
#define PROFILER_H

#include <string>
#include <map>
#include <chrono>

class Profiler
{
public:

    struct TimeData
    {
        TimeData() = default;
        std::chrono::time_point<std::chrono::system_clock> start, end;
        std::chrono::duration<double> elapsed_seconds;
    };

    void begin(const std::string& name)
    {
        const auto& it = mData.find(name);

        if (it != mData.end())
        {
            TimeData& time = it->second;

            time.start = std::chrono::system_clock::now();
        }
        else
        {
            TimeData time;

            time.start = std::chrono::system_clock::now();

            mData[name] = time;
        }

    }

    void end(const std::string& name)
    {
        const auto& it = mData.find(name);

        if (it != mData.end())
        {
            TimeData& time = it->second;

            time.end = std::chrono::system_clock::now();

            time.elapsed_seconds = time.end - time.start;
        }
    }

    double data(const std::string& name)const
    {
        const auto& it = mData.find(name);

        if (it != mData.end())
            return it->second.elapsed_seconds.count();

        return 0;
    }

    std::map<std::string, TimeData> mData;

}profiler;

#endif // PROFILER_H

Resource files:

test.txt

File system is huge subject need more work out. file is plain text file
This is some junk words. simple program for words counting in text file. 1 2 3
2 2 2 2
@ % & ^ *
all so good
this test text file

test1.txt

file system second file, test multiple files

Output for non-threaded version:

The most repeated element is: 2

 Word   Repeated Times
---------------------------
 2             5
 file          4
 text          3
 is            3
 system        2
 test          2
 File          1
 1             1
 need          1
 huge          1
 subject       1
 %             1
 more          1
 work          1
 plain         1
 This          1
 some          1
 program       1
 junk          1
 words.        1
 simple        1
 files         1
 for           1
 words         1
 counting      1
 in            1
 so            1
 file.         1
 3             1
 @             1
 &             1
 this          1
 ^             1
 file,         1
 *             1
 all           1
 good          1
 out.          1
 second        1
 mulitiple     1

0.0646741 seconds

Output for threaded version:

The first most repeated element is: 2

 Word   Repeated Times
---------------------------
 2             5
 file          4
 is            3
 text          3
 system        2
 test          2
 out.          1
 1             1
 need          1
 huge          1
 subject       1
 work          1
 more          1
 %             1
 plain         1
 This          1
 some          1
 junk          1
 program       1
 words.        1
 simple        1
 files         1
 for           1
 words         1
 counting      1
 in            1
 file.         1
 so            1
 3             1
 @             1
 &             1
 ^             1
 this          1
 file,         1
 *             1
 all           1
 good          1
 File          1
 second        1
 mulitiple     1

0.0776089 seconds
\$\endgroup\$
5
  • 1
    \$\begingroup\$ You could use <chrono> to replace the Windows-specific code. \$\endgroup\$
    – glampert
    Commented Dec 12, 2014 at 14:14
  • \$\begingroup\$ @glampert .. thanks it's even better. i updated the code \$\endgroup\$
    – MORTAL
    Commented Dec 12, 2014 at 14:45
  • 1
    \$\begingroup\$ To ignore punctuation automatically: stackoverflow.com/a/6154217/14065 \$\endgroup\$ Commented Dec 12, 2014 at 19:04
  • \$\begingroup\$ Why would you expect the threaded version to be faster. This is inherently a serial task (read a stream: Serial), (write a stream: serial). The only non serial task is sort and you have way to little data to make a parallel sort worth while. \$\endgroup\$ Commented Dec 12, 2014 at 19:15
  • \$\begingroup\$ @LokiAstari ... there is no particular reason. it's just common speaking we get used to hear. i agree and i believe the locale part of the streams need further study. i just had one year since i start learning C++ i have a lot to learn. i'm just at first step i have long way ahead \$\endgroup\$
    – MORTAL
    Commented Dec 13, 2014 at 7:34

1 Answer 1

2
\$\begingroup\$

Exceptions should be used in exceptional cases. If you had a long list of files I wouldn't really see it as an exceptional case that a file name was wrong or could not be opened. The function caller still might want data from other files, and when possible its good to avoid using try / catch.

Instead I would opt for a pass by reference and a return status. The pass by reference could then work on the same map every time to avoid the unnecessary copying of map elements.

bool loadFromFile(const std::string& fileName, Words& words)
{
    std::ifstream file(fileName);
    if (!file)
    {
        return false;
    }
    for (std::string word; file >> word;)
    {
        ++words[word];
    }
    return true;
}

I decided to use a bool because there are only two events that terminate the function; open file error, and a successful read. If you have more than two types of return events, use an integer or better yet use an enumeration!

int main(int argc, char *argv[])
{
    profiler.begin("test");
    // no need for resources variable
    Words words; 
    for (int i = 1; i < argc; ++i)
    {
        if(!loadFromFile(argv[i], words))
        {
            std::cerr << "Can't open the file " << argv[i] << std::endl;
            // Decide to error and kill the program here if need be.
        }
    }
    showResult(words.begin(), words.end());

    profiler.end("test");
    std::cout << '\n' << profiler.data("test") << " seconds\n";
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ I have always hated the phrase: Exceptions should be used in exceptional cases. It has no meaning. A better phrase: Exceptions should be used when an error can not corrected locally You can optimally be more specific with and requires control to passed up the call hierarchy to parent that can not be determined at compile time that can correct the error. \$\endgroup\$ Commented Dec 13, 2014 at 6:37
  • \$\begingroup\$ @LokiAstari I always presumed your definition there to be the meaning behind the phrase :p \$\endgroup\$
    – flakes
    Commented Dec 13, 2014 at 14:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.