高性能并行编程与优化 | 第02讲回家作业

简介: 本文是关于高性能并行编程与优化课程的第二讲回家作业,包括作业要求、初始运行结果、抄的答案以及改进后的运行结果。作业内容涉及对双链表类`List`的修改,要求避免不必要的拷贝、修复智能指针问题、实现深拷贝构造函数、解释为什么可以删除拷贝赋值函数以及改进`Node`的构造函数。同时,还提供了如何提交作业、设置https代理的链接以及评分规则。

一 作业要求

# 高性能并行编程与优化 - 第0x讲的回家作业

通过 pull request 提交作业。会批分数,但是:

没有结业证书,回家作业仅仅作为评估学习效果和巩固知识的手段,不必为分数感到紧张 :)
量力而行,只要能在本课中,学到昨天的自己不懂的知识,就是胜利,没必要和别人攀比。
注意不要偷看别人的作业哦!

- 课件:https://github.com/parallel101/course
- 录播:https://space.bilibili.com/263032155

作业提交时间不限 :) 即使完结了还想交的话我也会看的~ 不过最好在下一讲开播前完成。

- 如何开 pull request:https://zhuanlan.zhihu.com/p/51199833
- 如何设置 https 代理:https://www.jianshu.com/p/b481d2a42274

## 评分规则

- 完成作业基本要求 50 分(详见下方"作业要求")
- 能够在 PR 描述中用自己的话解释 25 分
- 代码格式规范、能够跨平台 5 分
- 有自己独特的创新点 20 分
- 明显抄袭现象 -100 分
- 改用 `unique_ptr` 10 分

## 作业要求

修改 main.cpp,改良其中的双链表类 `List`:

- 避免函数参数不必要的拷贝 5 分
- 修复智能指针造成的问题 10 分

- 实现拷贝构造函数为深拷贝 15 分
- 说明为什么可以删除拷贝赋值函数 5 分
- 改进 `Node` 的构造函数 5 分

并通过 `main()` 函数中的基本测试。

## 关于内卷

如果你把 List 改成了基于迭代器的,或是作为模板 `List`:
只要是在 **满足作业要求的基础** 上,这是件好事!
老师会酌情加分,视为“独特的创新点”,但最多不超过 20 分。

/* 基于智能指针实现双向链表 */
#include <cstdio>
#include <memory>

struct Node {
   
    // 这两个指针会造成什么问题?请修复
    std::shared_ptr<Node> next;
    std::shared_ptr<Node> prev;
    // 如果能改成 unique_ptr 就更好了!

    int value;

    // 这个构造函数有什么可以改进的?
    Node(int val) {
   
        value = val;
    }

    void insert(int val) {
   
        auto node = std::make_shared<Node>(val);
        node->next = next;
        node->prev = prev;
        if (prev)
            prev->next = node;
        if (next)
            next->prev = node;
    }

    void erase() {
   
        if (prev)
            prev->next = next;
        if (next)
            next->prev = prev;
    }

    ~Node() {
   
        printf("~Node()\n");   // 应输出多少次?为什么少了?
    }
};

struct List {
   
    std::shared_ptr<Node> head;

    List() = default;

    List(List const &other) {
   
        printf("List 被拷贝!\n");
        head = other.head;  // 这是浅拷贝!
        // 请实现拷贝构造函数为 **深拷贝**
    }

    List &operator=(List const &) = delete;  // 为什么删除拷贝赋值函数也不出错?

    List(List &&) = default;
    List &operator=(List &&) = default;

    Node *front() const {
   
        return head.get();
    }

    int pop_front() {
   
        int ret = head->value;
        head = head->next;
        return ret;
    }

    void push_front(int value) {
   
        auto node = std::make_shared<Node>(value);
        node->next = head;
        if (head)
            head->prev = node;
        head = node;
    }

    Node *at(size_t index) const {
   
        auto curr = front();
        for (size_t i = 0; i < index; i++) {
   
            curr = curr->next.get();
        }
        return curr;
    }
};

void print(List lst) {
     // 有什么值得改进的?
    printf("[");
    for (auto curr = lst.front(); curr; curr = curr->next.get()) {
   
        printf(" %d", curr->value);
    }
    printf(" ]\n");
}

int main() {
   
    List a;

    a.push_front(7);
    a.push_front(5);
    a.push_front(8);
    a.push_front(2);
    a.push_front(9);
    a.push_front(4);
    a.push_front(1);

    print(a);   // [ 1 4 9 2 8 5 7 ]

    a.at(2)->erase();

    print(a);   // [ 1 4 2 8 5 7 ]

    List b = a;

    a.at(3)->erase();

    print(a);   // [ 1 4 2 5 7 ]
    print(b);   // [ 1 4 2 8 5 7 ]

    b = {
   };
    a = {
   };

    return 0;
}

二 作业初始运行结果

三 抄的答案

改了很久,没成功,实在很烦,拖了2个月,还是抄答案吧。

/* 基于智能指针实现双向链表 
参考代码:https://github.com/parallel101/hw02/pull/53/files
*/
#include <cstdio>
#include <exception>
#include <memory>
#include <stdexcept>
#include <utility>
#include <type_traits>
#include <iterator>
#include <cstddef>

template <typename T>
struct Node {
   
    // 这两个指针会造成什么问题?请修复
    //     std::shared_ptr<Node> next;
    //     std::shared_ptr<Node> prev;
    // 对于双向链表,相邻项之间存在循环引用,引用计数无法归零释放空间。
    // 对于双向链表,可以看作前一项“拥有”后一项。
    // 而后一项保留前一项的Node*指针
    std::unique_ptr<Node> next;
    Node* prev;
    // 如果能改成 unique_ptr 就更好了!
    //int value;

     这个构造函数有什么可以改进的?
    //Node(int val) {
   
    //    value = val;
    //}

    //void insert(int val) {
   
    //    auto node = std::make_shared<Node>(val);
    //    node->next = next;
    //    node->prev = prev;
    //    if (prev)
    //        prev->next = node;
    //    if (next)
    //        next->prev = node;
    //}

    T value;

    // 这个构造函数有什么可以改进的?:value直接根据val构造而不是默认构造后赋值。
    Node(const T& val) : value(val), prev(nullptr) {
   }
    Node(const Node&) = default;
    Node& operator=(Node&&) = default;

    // insert会导致无法使用unique_ptr,因为会破环上面假设的“前一项拥有后一项”的前提
    /*
              +--- O ---+
        O ---x           x--- nextO
              +--- O ---+
        会变成上面这样, 双向链表中用不到该操作所以直接注掉了。
    */
    // void insert(int val) {
   
    //     auto node = std::make_unique<Node>(val);
    //     node->next = next;
    //     node->prev = prev;
    //     if (prev)
    //         prev->next = node;
    //     if (next)
    //         next->prev = node;
    // }


    void erase() {
   
        //if (prev)
        //    prev->next = next;
        if (next)
            next->prev = prev;
        if (prev)
            prev->next = std::move(next);
    }


    ~Node() {
   
        printf("~Node()\n");   // 应输出多少次?为什么少了?因为循环引用
    }
};

template<typename T>
struct List {
   
    //std::shared_ptr<Node> head;
    class iterator {
   
    public:
        using iterator_category = std::bidirectional_iterator_tag;
        using difference_type = std::ptrdiff_t;
        using value_type = Node<T>;
        using pointer = value_type*;  // or also value_type*
        using reference = value_type&;  // or also value_type&

        iterator(pointer ptr) : m_ptr(ptr) {
   }
        reference operator*() {
    return *m_ptr; }
        pointer operator->() {
    return m_ptr; }

        // Prefix increment
        iterator& operator++()
        {
   
            m_ptr = m_ptr->next.get(); return *this;
        }

        // Postfix increment
        iterator operator++(int)
        {
   
            iterator tmp = *this; ++(*this); return tmp;
        }

        friend bool operator== (const iterator& a, const iterator& b)
        {
   
            return a.m_ptr == b.m_ptr;
        };
        friend bool operator!= (const iterator& a, const iterator& b)
        {
   
            return a.m_ptr != b.m_ptr;
        };

    private:
        Node<T>* m_ptr;
    };

    std::unique_ptr<Node<T>> head;
    Node<T>* back = nullptr;

    List() = default;
    List(List const& other_) {
   
        List& other = const_cast<List&>(other_);//拷贝构造函数有特定的函数签名,不建议轻易改变。
        printf("List 被拷贝!\n");
        //head = other.head;  // 这是浅拷贝!
        // 请实现拷贝构造函数为 **深拷贝**
        for (auto it = other.begin(); it != other.end(); it++) {
   
            push_back(it->value);
        }
    }

    List &operator=(List const &) = delete;  // 为什么删除拷贝赋值函数也不出错?
    // 此处拷贝赋值 = 拷贝构造出右值+移动赋值

    List(List &&) = default;
    List &operator=(List &&) = default;

    //Node *front() const {
   
    Node<T>* front() const {
   
        return head.get();
    }

    //int pop_front() {
   
    //    int ret = head->value;
    //   head = head->next;
    T pop_front() {
   
        if (begin() == end()) {
   
            throw std::out_of_range("pop_front()");
        }
        T ret = head->value;
        if (head.get() == back)
            back = nullptr;
        head = std::move(head->next);
        return ret;
    }

    //void push_front(int value) {
   
    //    auto node = std::make_shared<Node>(value);
    //    node->next = head;
    void push_front(const T& value) {
   
        auto node = std::make_unique<Node<T>>(value);
        if (head)
     //       head->prev = node;
    //    head = node;
        head->prev = node.get();
        else
        back = node.get();
        node->next = std::move(head);
        head = std::move(node);
    }

   // Node *at(size_t index) const {
   
    void push_back(const T& value) {
   
        auto node = std::make_unique<Node<T>>(value);
        if (back) {
   
            node->prev = back;
            back->next = std::move(node);
            back = back->next.get();
        }
        else {
   
            head = std::move(node);
            back = head.get();
        }
    }

    Node<T>* at(size_t index) const {
   
        auto curr = front();
        for (size_t i = 0; i < index; i++) {
   
            curr = curr->next.get();
        }
        return curr;
    }

    iterator begin() {
    return iterator(head.get()); }
    iterator end() {
    return iterator(nullptr); }
};

void print(List<int> & lst) {
     // 有什么值得改进的?: 传入const引用,避免拷贝
    printf("[");
  //  for (auto curr = lst.front(); curr; curr = curr->next.get()) {
   
  //      printf(" %d", curr->value);
    for (auto &v : lst) {
   
        printf(" %d", v.value);
    }
    printf(" ]\n");
}

int main() {
   
    //List a;
    List<int> a;

    a.push_front(7);
    a.push_front(5);
    a.push_front(8);
    a.push_front(2);
    a.push_front(9);
    a.push_front(4);
    a.push_front(1);

    print(a);   // [ 1 4 9 2 8 5 7 ]

    a.at(2)->erase();

    print(a);   // [ 1 4 2 8 5 7 ]

    //List b = a;
    List<int> b = a;

    a.at(3)->erase();

    print(a);   // [ 1 4 2 5 7 ]

    b.push_back(1);
    b.push_back(2);
    b.push_back(3);
    print(b);   // [ 1 2 3 ]
    print(b);   // [ 1 4 2 8 5 7 ]

    b = {
   };
    a = {
   };

    return 0;
}

四 改进的运行结果

相关文章
|
2月前
|
C++
高性能并行编程与优化 | 第01讲回家作业
本文是关于高性能并行编程与优化的回家作业,涉及CMake错误解决、编译问题处理、代码和编译结果分享、使用方法说明以及躺坑记录。
高性能并行编程与优化 | 第01讲回家作业
|
2月前
高性能并行编程与优化 | 第03讲回家作业
本文是关于高性能并行编程与优化课程的第三讲回家作业,包括题目要求、代码答案抄写以及成功运行的截图。
高性能并行编程与优化 | 第03讲回家作业
|
2月前
|
算法 编译器
高性能并行编程与优化 | 第04讲回家作业
本文是关于高性能并行编程与优化课程的第四讲回家作业,包括作业要求、原始代码运行结果、优秀的代码答案以及优化后的运行结果和解析。作业目标是利用所学知识优化多体引力求解器的代码,要求不能使用多线程并行和算法复杂度优化,但可以通过编译器和平台优化来提升性能。
高性能并行编程与优化 | 第04讲回家作业
|
6月前
|
缓存 Rust 安全
rcore 笔记 批处理系统 邓氏鱼(一)
rcore 笔记 批处理系统 邓氏鱼
130 0
|
6月前
|
存储 缓存 Rust
rcore 笔记 批处理系统 邓氏鱼(二)
rcore 笔记 批处理系统 邓氏鱼
75 0
|
6月前
|
Rust 数据处理
rcore 笔记 批处理系统 邓氏鱼(三)
rcore 笔记 批处理系统 邓氏鱼
102 0
|
人工智能 自然语言处理 数据可视化
搭建批处理系统
搭建批处理系统
141 0
搭建批处理系统
|
消息中间件 缓存 负载均衡
Kafka性能调优实战:同等资源配置性能提升20几倍的秘诀
Kafka性能调优实战:同等资源配置性能提升20几倍的秘诀
Kafka性能调优实战:同等资源配置性能提升20几倍的秘诀
|
编译器
【计算机组成原理】从CPU执行时间聊如何做性能优化
衡量性能的指标有什么?针对CPU执行时间,我们可以从哪些部分优化?
546 0