为什么std::valarray会很慢。

本文作者通过对比实验发现,std::valarray在执行计算时的性能显著低于普通循环和直接使用指针访问。尽管std::valarray提供了丰富的操作,但在VCdebug模式下,由于额外的检查导致性能下降。后续文章将探讨优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(sguox002原创作品,欢迎批评,转载请保留作者信息)

std::valarray有着很多吸引人的特性。比如下标数组,切割,逻辑,算术运算。我们慢慢学习。

在这之前,我想看一下这个类的性能如何,因此我编了一个小程序,来做同样的计算。结果发现valarray要比普通的循环要慢得多。我将会在另一片文章中说明如何解决这个问题。

#include <iostream>

#include <valarray>
#include <iostream>
#include "windows.h"


using namespace std ;


class hptime
{
LARGE_INTEGER sys_freq;
public:
hptime(){QueryPerformanceFrequency(&sys_freq);}   


double gettime()
{
LARGE_INTEGER tick;
QueryPerformanceCounter(&tick);
return (double)tick.QuadPart*1000.0/sys_freq.QuadPart;
}
};


int main()
{
hptime t0;
    enum { N = 5*1024*1024 };
    valarray<double> a(N), b(N), c(N) ;
    int i,j;
    for(  j=0 ; j<8 ; ++j )
    {
        for(  i=0 ; i<N ; ++i ) 
        {
            a[i]=rand();
            b[i]=rand();
        }


        double* a1 = &a[0], *b1 = &b[0], *c1 = &c[0] ;
        double dtime=t0.gettime();
        for(  i=0 ; i<N ; ++i ) c1[i] = a1[i] * b1[i] ;
        dtime=t0.gettime()-dtime;
        cout << "double operator* " << dtime << " ms\n" ;


        dtime=t0.gettime();
        c = a*b ;
        dtime=t0.gettime()-dtime;
        cout << "valarray operator* " << dtime << " ms\n" ;


        dtime=t0.gettime();
        for(  i=0 ; i<N ; ++i ) c[i] = a[i] * b[i] ;
        dtime=t0.gettime()-dtime;
        cout << "valarray[i] operator* " << dtime<< " ms\n" ;


        cout << "------------------------------------------------------\n" ;
    }

}


打开优化运行结果为:

double operator* 60.655 ms
valarray operator* 149.445 ms
valarray[i] operator* 37.7197 ms
------------------------------------------------------
double operator* 33.4131 ms
valarray operator* 113.355 ms
valarray[i] operator* 37.4807 ms
------------------------------------------------------
double operator* 32.4348 ms
valarray operator* 111.492 ms
valarray[i] operator* 38.5 ms
------------------------------------------------------
double operator* 32.9003 ms
valarray operator* 112.317 ms
valarray[i] operator* 37.0661 ms
------------------------------------------------------


这个程序中有几点值得指出:

1。类的私有成员仍然可以通过指针直接存取,C++只是提供编译时的存取控制,从概念上实现封装,但是它不能防止任何运行时的存取。

2。VCdebug模式的运行结果更慢,可能10倍以上。是因为类里的assert等检查都会严重影响性能。(The reason of slowness stated here is not true in this case, see the other article Debug&Release for details-----by the author)

3。高精度时钟,其精度在微秒。



解释://studentc.h #ifndef STUDENTC_H_ #define STUDENTC_H_ #include <iostream> #include <string> #include <valarray> class Student { private: // 定义别名 typedef std::valarray<double> ArrayDb; std::string name; ArrayDb scores; std::ostream & arr_out(std::ostream & os) const; public: Student(): name("Default"), scores() {} // 防止隐式转换 explicit Student(const std::string & s) : name(s), scores(){}; explicit Student(int n) : name("Default"), scores(n) {} Student(const std::string & s, int n): name(s), scores(n){} Student(const std::string & s, const ArrayDb & a): name(s), scores(a){} Student(const char * str, const double *pd, int n): name(str), scores(pd, n){} // 析构函数 ~Student() {} double Average() const; const std::string & Name() const; double & operator[](int i); double operator[](int i)const; // 友元函数 friend std::istream & operator>>(std::istream & is, Student & stu); friend std::istream & getline(std::istream & is, Student & stu); friend std::ostream & operator<<(std::ostream & os, Student & stu); }; #endif // studentc.cpp #include "student.h" using std::ostream; using std::endl; using std::istream; using std::string; double Student::Average() const { if(scores.size() > 0) return scores.sum() / scores.size(); else return 0; } const string & Student::Name() const { return name; } double & Student::operator[](int i) { // 使用的是 valarray<double>::operator[]()方法 return scores[i]; } double Student::operator[](int i) const { return scores[i]; } ostream & Student::arr_out(ostream & os) const { int i; int lim = scores.size(); if(lim > 0) { for(i = 0; i < lim; i++) { os << scores[i] << " "; if(i % 5 == 4) os << endl; } if(i % 5 != 0) os << endl; } else { os << " empty array "; } return os; } // 友元函数 istream & operator>>(istream & is, Student & stu) { // 调用的是string的operator>>() is >> stu.name; return is; } istream & getline(istream & is, Student & stu) { // 使用的是string的友元getline(istream &, const string &) getline(is, stu.name); return is; } ostream & operator<<(ostream & os, Student & stu) { os << "Scores for " << stu.name << ": " << endl; // 使用的是私有方法arr_out stu.arr_out(os); return os; }// use_stuc.cpp // compile with studentc.cpp #include <iostream> #include "student.h" using std::cin; using std::cout; using std::endl; void set(Student & sa, int n); const int pupils = 3; const int quizzes = 5; int main() { // 使用quizzes初始化Student, 然后再初始化一个ada数组里面是三个Student对象 Student ada[pupils] = {Student(quizzes), Student(quizzes), Student(quizzes)}; int i; for(i = 0; i < pupils; i++) set(ada[i], quizzes); cout << "Student List : " << endl; for(i = 0; i < pupils; i++) cout << ada[i].Name() << endl; cout << endl << "Results:"; for(i = 0; i < pupils; i++) { cout << endl << ada[i]; cout << "average: " << ada[i].Average() << endl; } cout << "Done" << endl; return 0; } void set(Student & sa, int n) { cout << "Please enter the student's name : "; getline(cin, sa); cout << "Please enter " << n << " quiz scores: " << endl; for(int i = 0; i < n; i++) cin >> sa[i]; while(cin.get() != '\n') continue; }编写他们三个的makefile文件
06-03
<think>好的,我现在需要帮用户解决如何为三个C++文件(studentc.h、studentc.cpp、use_stuc.cpp)编写Makefile的问题。用户之前的问题是关于两个C++文件共用头文件的Makefile编写,现在扩展到三个文件,不过结构应该类似。首先,我得回顾用户提供的引用内容。引用[2]提到Makefile的作用是自动化编译,避免手动逐个编译,特别是当文件之间有依赖关系时。用户之前的回答中已经讲解了如何编写基础Makefile,包括定义编译器、目标文件依赖关系等。现在用户的新问题涉及三个文件,结构应该类似,但需要确认每个源文件是否正确包含头文件。接下来,用户提供的引用[1]中提到了使用extern声明常量,这可能与头文件中的内容相关,比如studentc.h是否包含类似的外部声明。不过这可能不影响Makefile的结构,但需要确保头文件被正确包含在源文件中。 用户现在的三个文件是studentc.h、studentc.cpp和use_stuc.cpp。其中,studentc.cpp和use_stuc.cpp都包含了studentc.h。因此,当studentc.h被修改时,这两个源文件都需要重新编译。这和之前的例子类似,只是多了一个源文件。我需要按照之前的思路,编写一个基础的Makefile,定义编译器和选项,然后设置目标文件的依赖关系。每个.o文件都依赖于对应的.cpp文件和头文件。然后链接所有.o文件生成可执行文件。 可能需要注意的地方是,确保每个源文件都正确列出对头文件的依赖。例如,studentc.o依赖studentc.cpp和studentc.h,use_stuc.o依赖use_stuc.cpp和studentc.h。这样,当头文件修改时,两个.o文件都会重新生成。 另外,用户之前的回答提到了优化依赖管理,比如使用g++-MM自动生成依赖关系。但用户现在的问题可能只需要一个基础的Makefile,所以可能不需要引入高级功能,保持简单明了。 然后,需要验证Makefile是否正确,包括执行make命令是否生成可执行文件,修改头文件后是否触发重新编译,以及makeclean是否清理生成的文件。最后,生成相关问题,可能涉及如何管理多个文件、Makefile变量使用、清理命令的作用,以及使用CMake替代Makefile等。这些问题需要与当前主题相关,帮助用户进一步学习。</think>### 分步解答:为包含 `studentc.h` 的 C++ 项目编写 Makefile #### 步骤1:确认文件依赖关系 假设项目文件结构如下: ``` project/ ├── studentc.h // 类/结构体声明 ├── studentc.cpp // 类方法实现 └── use_stuc.cpp // 主程序 ``` 依赖关系: - `studentc.cpp` 和 `use_stuc.cpp` 均包含 `#include "studentc.h"` - 修改 `studentc.h` 时,两个 `.cpp` 文件均需重新编译[^2] --- #### 步骤2:基础 Makefile 编写 ```makefile # 定义编译器和编译选项 CXX = g++ CXXFLAGS = -Wall -std=c++11 # 最终目标可执行文件 student_app: studentc.o use_stuc.o $(CXX) $(CXXFLAGS) -o $@ $^ # 目标文件依赖规则 studentc.o: studentc.cpp studentc.h $(CXX) $(CXXFLAGS) -c $< use_stuc.o: use_stuc.cpp studentc.h $(CXX) $(CXXFLAGS) -c $< # 清理生成文件 clean: rm -f student_app *.o ``` #### 关键点解释: 1. **依赖关系显式声明** 每个 `.o` 文件明确列出对应的 `.cpp` 和 `.h` 文件,确保头文件修改时触发重新编译[^2] 2. **自动变量简化规则** - `$@` 表示目标文件名(如 `studentc.o`) - `$<` 表示第一个依赖文件名(如 `studentc.cpp`) - `$^` 表示所有依赖文件(如 `studentc.o use_stuc.o`)[^4] 3. **编译与链接分离** `-c` 标志实现分离编译,提高编译效率(仅需重新编译修改的模块) --- #### 步骤3:验证 Makefile 1. **首次编译** ```bash make ``` 生成 `student_app` 可执行文件 2. **修改头文件验证依赖** 修改 `studentc.h` 后再次执行 `make`,观察是否重新编译两个 `.cpp` 文件 3. **清理中间文件** ```bash make clean ``` --- ### 进阶优化(自动依赖生成) 对于更复杂的项目,使用 `g++ -MM` 自动生成依赖关系: ```makefile CXX = g++ CXXFLAGS = -Wall -std=c++11 SRCS = studentc.cpp use_stuc.cpp OBJS = $(SRCS:.cpp=.o) DEPS = $(SRCS:.cpp=.d) student_app: $(OBJS) $(CXX) $(CXXFLAGS) -o $@ $^ %.o: %.cpp $(CXX) $(CXXFLAGS) -MMD -c $< -o $@ -include $(DEPS) clean: rm -f student_app *.o *.d ``` - `-MMD` 选项自动生成 `.d` 依赖文件 - `-include` 包含所有依赖规则,无需手动维护[^4] --- ### 示例文件内容参考 `studentc.h`(头文件): ```cpp #ifndef STUDENTC_H #define STUDENTC_H struct Student { int id; char name[50]; }; void printStudent(const Student& s); #endif ``` `studentc.cpp`(实现): ```cpp #include "studentc.h" #include <iostream> void printStudent(const Student& s) { std::cout << "ID: " << s.id << ", Name: " << s.name << std::endl; } ``` `use_stuc.cpp`(主程序): ```cpp #include "studentc.h" int main() { Student s{1, "Alice"}; printStudent(s); return 0; } ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值